Posts RSS Comments RSS 117 Posts and 170 Comments till now

Archive for February, 2008

My Own Scripting Games!

I haven’t had time to participate in the 2008 and I have kinda missed out. But alas… a need has come through for me.

This is what I am looking for



I need to create a Test AD environment. I don’t need to mimic a specific production AD, but more like all of them :) I want an AD environment that has plenty of user/computer accounts and plenty of Groups. I also want a large number of OUs for testing Group Policy Scripts.
These need to be realistic numbers so here is the challenge I came up with (note, Quest tools are allowed!)



# A TestOU OU
# A TestComputers OU
# A TestUsers OU
# A TestGroups OU
# 10K OU’s Under TestOU
## Each of the 10k OUs will have 4 Child OUs
### Each OU should have 5 users Accounts and 5 Machines Accounts
# Create 500 Group Policies.
# Link 100 policies on the 10k Base OUs
# Create 2000 Users in the TestUser OU
# Create 2000 Computers in the TestComputer OU
# Find all the Users
# Create 2K Groups
## Add Even Numbered Users to Even Groups
## Add Odd Numbered Users to Odd Groups


I will post my solution as soon as I am done, but I would like other people’s thoughts so POST away!

Interpreting > Parsing (who knew!)

I found out some interesting information yesterday. I thought I would share it.

Back Story:
A question was asked on how to include $env:ComputerName to a string. I quickly piped up “HA! That is easy just wrap @() around the variable and it will work.” While this indeed works and is what I have been doing for the last couple of years… There was a better way!

It turns out that Jim Truher was watching this particular thread discussion and quickly informed me that this was “quite inefficient” and I should use ${} instead. So of course, I had to know why. Luckily Bruce Payette was also watching this thread and explained the following.

I hope he doesn’t mind if I quote him.

“The big difference is whether the parser gets involved or not. In the ${foo} case, the interpreter simply extracts the string, looks up the variable and inserts the result. In the $($var) case, the string is extracted, parsed and then executed which is more overhead.”

More Info (provided by Keith Hill):
$() is used within strings to evaluate statements (potentially multiple statements) and the results are converted into a string (Using OFS.)

${} is simply used to prevent the interpreter from “interpreting” the name of the variable instead it is treated as the literal name of the variable

Moral of the Story?
From now on I am using ${} when no evaluating is needed.

Jims Blog
http://jtruher.spaces.live.com/

Keith Hill has an awesome Series called Effective Powershell
http://keithhill.spaces.live.com/

Bruce hangs out on the Powershell Forum
http://blogs.msdn.com/PowerShell/

Bruce also wrote Windows Powershell in Action
http://www.manning.com/payette/

p.s. I tested this with this code

  1. # with ${}
  2. ( Measure-Command {1..10000 | %{ "${env:path}" } } ).TotalMilliseconds
  3. # vs with $()
  4. ( Measure-Command {1..10000 | %{ "$($env:path)" } } ).TotalMilliseconds

SpecOps and Group Policies… What a match!

Special Operations Software has created an Incredible marriage of Powershell and Group Policy. Please take some time to watch these Demos. AWESOME!

Specops Command done by Darren Mar-Elia:
http://www.specopssoft.com/powershell/specopscommand-sdm.wmv

Specops Deploy done by Derek Melber:
http://www.specopssoft.com/products/specopsdeploy/specops_deploy.wmv

To Hex and Back (The Journey of an Integer)

This is so simple and I forget it every time. This time I am blogging it! (I apologize if the title has been taken before.)

From HEX to Integer
Start Value: 124f80

To convert to Integer we just add 0x and Powershell will do the rest

  1. 0×124f80
  2. 1200000

From Integer to Hex
Start Value: 1234567

To Convert to HEX we can use the -f operator

  1. "{0:x}" -f 1234567
  2. 12d687

UPDATE!

Oisin pointed out these options as well

  1. PS: (4545).tostring("x")
  2. 11c1
  3.  
  4. # or
  5.  
  6. PS: $n = 4545; $n.tostring("X")
  7. 11C1

The Power of LDAP Filters

A common problem when dealing with Active Directory is the end user trying to parse the results themselves.

Let take this example

  1. $selector = New-Object DirectoryServices.DirectorySearcher
  2. $selector.SearchRoot = [ADSI]""
  3. $selector.pagesize = 1000
  4. $adobj= $selector.findall() | where {$_.properties.objectcategory -match "CN=Person"}
  5. foreach ($person in $adobj) {
  6.    $date120DaysAgo = [DateTime]::Now.AddDays(-120).ToFileTime()
  7.    $LL1 = $person.properties.lastlogontimestamp
  8.    if(($LL1 -le $date120DaysAgo) -and ($person.GetDirectoryEntry().psbase.invokeget(‘AccountDisabled’))){$person}
  9. }

Instead of doing the parsing on results side… we should let the server do the work. How do we do that?

With LDAP filters. Here is an example.

  1. $date = (Get-Date).AddDays(-120).ToFileTime()
  2. $filter = "(&(objectcategory=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(lastlogontimestamp<=$date))"
  3. $ds = New-Object DirectoryServices.DirectorySearcher([ADSI]"",$filter)
  4. $ds.PageSize = 1000
  5. $users = $ds.FindAll()
  6. $users

Or with Quest tools… even easier!

  1. PS: $date = (Get-Date).AddDays(-120).ToFileTime()
  2. PS: $filter = "(&(objectcategory=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(lastlogontimestamp<=$date))"
  3. PS: Get-QADUser -LdapFilter $filter

I think you will find with an LDAP filter you can save a TON of time.

Here is the output of measure-command for the two examples above (this was a very small sample.)

Without Filter
————–
Days : 0
Hours : 0
Minutes : 0
Seconds : 3
Milliseconds : 477
Ticks : 34776670

With Filter
———–
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 34
Ticks : 340740

If you were to do this on a large AD the difference in time would be HUGE! Here is an example with 600K users…

With Filter
————
Days : 0
Hours : 0
Minutes : 0
Seconds : 17
Milliseconds : 353
Ticks : 173535605

I can’t post one with out filter… because it has been hours and it is still not done :)

MoW directed me to a Good 4+ part Series on Citrix Workflow Studio

It can be found here:
http://www.frameworkx.com/blogpost.aspx?id=2&c=1128

MoW Post is Here: http://thepowershellguy.com/blogs/posh/archive/2008/02/14/citrix-workflow-studio-part-2.aspx

Citrix Takes the Next Step…

The Next Step towards a Powershell Management Structure for Citrix

Citrix Workflow Studio.
http://www.citrix.com/English/ps2/products/product.asp?contentID=1297816

I am not sure how big of a win this is for Powershell and from what I have seen this seems to be a licensing or purchase of Full Armor, but it is a start.

Keep your eyes open :)

Calculated Properties (UPDATE!)

I had a user point out that my code didnt work in a 4.5 farm. I did some testing and while some information was there, it was not complete. I did Test XP and 4.0 and they worked fine. I created a new one for CTX PS 4.5

  1. $MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
  2. $MF.Initialize(1)
  3. $MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,DistinguishedName,
  4.   @{n="AppName";e={$_.WinAppObject.BrowserName}},
  5.   @{n=‘Users’; e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 2} | %{$_.AccountName}}},
  6.   @{n=‘Groups’;e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 4} | %{$_.AccountName}}}

Lets talk about whats going on here

First We Get the MFCom Farm Object and Initialize it.

  1. $MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
  2. $MF.Initialize(1)

Next we iterate throught the servers parsing out only the ServerName and Getting MFCom Application Objects and pipe it along

  1. $MF.Servers | Select-Object ServerName -expand Applications |

The Final part is where we make the changes. In 4.5 the Application object no longer has Users Property or Group Property. It also no longer uses AppName.
For AppName we use: $_.WinAppObject.BrowserName
For Users we use: Accounts2 with AccountType 2
For Groups we use: Accounts2 with AccountType 4
The User/Group Name is stored in a property called AccountName

You will also notice the LoadData. This because not all information is there be default. LoadData fills out the object with data.

  1.  # Get the AppName from BrowserName
  2.   @{n="AppName";e={$_.WinAppObject.BrowserName}},
  3.   # Get users from Accounts2 filtering type 2 and selecting AccountName
  4.   @{n=‘Users’; e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 2} | %{$_.AccountName}}},
  5.   # Get Groups from Accounts2 filtering type 4 and selecting AccountName
  6.   @{n=‘Groups’;e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 4} | %{$_.AccountName}}}

I dont normally do this, but this is a big one.

Win 2008 and Vista SP1 RTM
http://blogs.technet.com/windowsserver/archive/2008/02/04/windows-server-2008-rtm.aspx

Or at least it appears so… MS hasnt made if official yet.