Posts RSS Comments RSS 117 Posts and 170 Comments till now

My First Venture into S.DS.P and Powershell

There has be much debate and agony on the the slowness of System.DirectoryServices.DirectorySearcher Class. This has lead me down the path of playing with System.DirectoryServices.Protocols (aka s.ds.p.)

By far the best tool out there right now is ADFind by joe Richards. So I used this for my target (although I didnt expect to get close.)

Here is the Guide I used in my Journey:
Introduction to System.DirectoryServices.Protocols (S.DS.P)

I started by writing the script you will find below and tested in a domain with well over 700k users and one with 200K. I did the test three different times in each Domain changing the order so cache hits wouldn’t be an issue. I also use objectclass on all three so index wouldn’t be a factor. I should NOTE that my script ONLY returns the count ATM. I am going to add properties next.

As you will see by the test results below… I got pretty darn close to adfind.exe in regards to perf. I was quite impressed with S.DS.P. Now to be fair, this was just counting objects. I am sure adfind.exe will start sneaking ahead abit further when we start processing properties and such. Can’t wait to see! Understand these test were just to see what tests I should focus on. I will posting another entry on more extensive count test with just DSP Using 1.1 and Adfind.

Test 1 700K Users done Remote

DirectorySearcher : 125.9123257
ADFind : 46.3763349
DSP Using DN : 69.5628776
DSP Using 1.1 : 49.4458161

Test 2 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 46.4486472
DSP Using DN : 68.9255288
DSP Using 1.1 : 49.0780736

Test 3 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 47.9162918
DSP Using DN : 79.6885386
DSP Using 1.1 : 54.152966

Test 1 200K done Local

DirectorySearcher : 121.1063569
ADFind : 55.2775406
DSP Using DN : 67.897922
DSP Using 1.1 : 28.5441615

Test 2 200K done Local

DirectorySearcher : 80.0894455
ADFind : 23.558696
DSP Using DN : 54.3111576
DSP Using 1.1 : 42.3485998

Test 3 200K done Local

DirectorySearcher : 99.1125363
ADFind : 80.1497852
DSP Using DN : 64.3716824
DSP Using 1.1 : 64.1940421

Summary: adfind.exe was faster (by bout 4sec on Avg.) remotely and larger domain, but protocals was faster (by bout 8 sec on Avg.) local on smaller domain.
Remote

ADFind: 46.92
DSP Using 1.1: 50.89
DSP Using DN: 72.73
DirectorySearcher: 125.32

Local

DSP Using 1.1: 45.03
ADFind: 52.99
DSP Using DN: 62.19
DirectorySearcher: 100.10

Here the script I ran for the test and how I measured the commands. I am going to play with passing the stats control and see what the server says later.

  1. $SearcherExpression = @’
  2. $searcher = new-object System.DirectoryServices.DirectorySearcher([ADSI]"","(objectclass=user)",@("distinguishedName"))
  3. $searcher.pagesize = 1000
  4. $searcher.findall()
  5. ‘@
  6.  
  7. Write-Host "Test 1"
  8. Write-Host ("-"*40)
  9. $myresults1 = "" | select @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  10.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  11.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  12.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}}
  13. $myresults1 | fl
  14.  
  15. Write-Host "Test 2"
  16. Write-Host ("-"*40)
  17. $myresults2 = "" | select @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
  18.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  19.                          @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  20.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}}
  21.  
  22. $myresults2 | fl
  23.  
  24. Write-Host "Test 3"
  25. Write-Host ("-"*40)
  26. $myresults3 = "" | select @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
  27.                          @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
  28.                          @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
  29.                          @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}}
  30. $myresults3 | fl
  31.  
  32. $myresults1,$myresults2,$myresults3
  33.  

Here is what the output of that Script looks like

S.DS.P : MyTest.ps1 Output

Here is the System.DirectoryServices.Protocols Code

  1. [System.Reflection.assembly]::LoadWithPartialName("system.directoryservices.protocols") | Out-Null
  2. $domain = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=",""
  3. $DomainDN = "DC=" + $Domain -replace "\.",",DC="
  4. [int]$pageCount = 0
  5. [int]$pageSize = 1000
  6. [int]$count = 0
  7. $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
  8. $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
  9. $filter = "(objectclass=user)"
  10. $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,@("1.1"))  
  11. $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
  12. $searchRequest.Controls.add($pagedRequest) | out-null
  13. $searchOptions = new-object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
  14. $searchRequest.Controls.Add($searchOptions) | out-null
  15.  
  16. while ($true)
  17. {
  18.     ## increment the pageCount by 1
  19.     $pageCount++
  20.     ## cast the directory response into a
  21.     ## SearchResponse object
  22.     $searchResponse = $connection.SendRequest($searchRequest)
  23.     ## verify support for this advanced search operation
  24.     if (($searchResponse.Controls.Length -lt 1) -or
  25.         !($searchResponse.Controls[0] -is [System.DirectoryServices.Protocols.PageResultResponseControl]))
  26.     {
  27.         Write-Host "The server cannot page the result set"
  28.         return;
  29.     }
  30.     ## cast the diretory control into
  31.     ## a PageResultResponseControl object.
  32.     $pageResponse = $searchResponse.Controls[0]
  33.     ## display the retrieved page number and the number of
  34.     ## directory entries in the retrieved page                    
  35.     #"Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
  36.     $count += $searchResponse.entries.count
  37.     ## display the entries within this page
  38.     ## foreach($entry in $searchResponse.entries){$entry.DistinguishedName}
  39.     ## if this is true, there
  40.     ## are no more pages to request
  41.     if ($pageResponse.Cookie.Length -eq 0){write-Host $count;break}
  42.     ## set the cookie of the pageRequest equal to the cookie
  43.     ## of the pageResponse to request the next page of data
  44.     ## in the send request
  45.     $pagedRequest.Cookie = $pageResponse.Cookie
  46. }

Get-CitrixApplication (Playing Around Series)

Here is a quick demo of getting a Citrix Application and playing with its properties.

Best Viewed Full Screen
Get the Flash Player to see this player.

Demo File

  1. #
  2. # First we need to create the MFCOM Object
  3. #
  4. $mfapp = new-object -com MetaFrameCom.MetaFrameApplication
  5. #
  6. # To initialize we need to pass the app we want to accesss
  7. #
  8. $mfapp.Initialize(3,"Applications\Powershell")
  9. #
  10. # With Applications we need to load the data
  11. #
  12. $mfapp.loaddata(1)
  13. #
  14. # Lets see what we have
  15. #
  16. $mfapp | Get-Member -type Properties
  17. #
  18. # Lets look at Users and Groups
  19. #
  20. $mfapp | select Users,Groups
  21. #
  22. # How bout Servers
  23. #
  24. $mfapp.Servers | Select ServerName
  25. #
  26. # Sessions?
  27. #
  28. $mfApp.Sessions | ft SessionID,AppName,ClientAddress,ClientHRes,ClientVRes -auto

Get-CitrixFarm (Playing Around Series)

I wanted to show how EASY it is to play with Citrix MFCom so here is a little video.

I also want to note how most of the properties (like Servers,Applications,Zones) all return objects that have their own properties and methods. So you could very easily have these lines in your profile and always have everything just sitting there waiting to be used.

  1. $farm = New-Object -Com ‘MetaframeCOM.MetaFrameFarm’
  2. $farm.Initialize(1)
  3. Write-Host "Loaded Farm Info from $($farm.FarmName)

Best Viewed Full Screen
Get the Flash Player to see this player.

Demo File

  1. # Get Citrix Farm Object
  2. #
  3. $farm = New-Object -Com ‘MetaframeCOM.MetaFrameFarm’
  4. #
  5. # Initialize Farm
  6. #
  7. $farm.Initialize(1)
  8. #
  9. # Now that we have are farm. Lets make sure we have the one we want by Getting the FarmName
  10. $Farm.FarmName
  11. #
  12. # Lets see what we have to play with
  13. #
  14. $farm | Get-Member -type Properties
  15. #
  16. #
  17. # We have the Farm we want. Some of the Info we want is Admins. So lets Start there
  18. #
  19. $farm.Admins
  20. #
  21. # To View just a list
  22. #
  23. $farm.Admins | Select FriendlyName
  24. #
  25. # Lets see what Applications we have
  26. #
  27. $farm.Applications | ft BrowserName,ParentFolderDN
  28. #
  29. # To View the Servers
  30. #
  31. $farm.Servers | ft ServerName,IPAddress,SessionCount
  32. #
  33. # How bout Sessions?
  34. $farm.Sessions
  35. #
  36. # Lets look at Print Drivers we Have installed
  37. #
  38. $farm.Drivers
  39. #
  40. # If you have multiple Zones you can get the Names Servers and DataCollector for the Zone
  41. $farm.Zones
  42. #

Get-CitrixAppServer.ps1 (Citrix Top 10)

This is pretty simple script. It is actually just an adaption from my Get-CitrixApplication.ps1 script posted at the bottom. There is a moment in time when you start thinking in terms of objects and stop thinking in terms of text output you are looking for.

This will make a huge difference in productivity and take you from constantly writing scripts to interactively getting the information you are after, but until you make the transition I will continue to provide scripts that do both :)

  1. # Get-CitrixAppServer.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Gets All the Servers for Specific App
  5. Param($app)
  6. $mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
  7. $mfApp.Initialize(3,$app)
  8. $mfApp.LoadData(1)
  9. $mfApp.Servers | %{$_.ServerName}

Here is the script that I use to return an Application object.

  1. # Get-CitrixApplication.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Gets a Citrix Application Object.
  5. Param($app)
  6. $mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
  7. $mfApp.Initialize(3,$app)
  8. $mfApp.LoadData(1)
  9. $mfApp

Then from the commandline I would just do this
PS> Get-CitrixApplication.ps1 “Applications\MyApp1″ | %{$_.Servers} | %{$_.ServerName}

While this looks more complicated it is more versatile and easy to change. Lets say I want current User for this app instead.
PS> Get-CitrixApplication.ps1 “Applications\MyApp1″ | %{$_.Sessions} | %{$_.UserName}

Get-CitrixPrinterInfo.ps1 (Citrix Top 10)

This script just collects the Printer Information from all the Servers in the Farm. It will output them to a file or you can pipe them and filter them.

Name: Get-CitrixPrinterInfo.ps1
Purpose: Gets Print Driver Info from all the Servers in the Farm and outputs to file or console

  1. # Get-CitrixPrinterInfo.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Gets Print Driver Info from all the Servers in the Farm
  5. Param($Server,$file,[Switch]$help)
  6. function HelpMe{
  7.     Write-Host
  8.     Write-Host " Get-CitrixPrinterInfo.ps1:" -fore Green
  9.     Write-Host "   Gets Print Driver Info from all the Servers in the Farm"
  10.     Write-Host
  11.     Write-Host " Parameters:" -fore Green
  12.     Write-Host "   -Server                : Optional. Server to Get Print Info From "
  13.     Write-Host "   -File                  : Optional. File Name to Export Info to"
  14.     Write-Host "   -Help                  : Optional. Displays This"
  15.     Write-Host
  16.     Write-Host " Examples:" -fore Green
  17.     Write-Host "   Gets Print Info and Exports to File" -fore White
  18.     Write-Host "     .\Get-CitrixPrinterInfo.ps1 -file MyExportFile.txt" -fore Yellow
  19.     Write-Host
  20.     Write-Host "   Gets Print Drivers from a Specific Server and outputs DriverName and SourceServer"  -fore White
  21.     Write-Host "     .\Get-CitrixPrinterInfo.ps1 <serverName> | ft DriverName,SourceServer" -fore Yellow
  22.     Write-Host
  23. }
  24.  
  25. # Check for Help Flag
  26. if($help){HelpMe;Write-Host;Return}
  27.  
  28. # Check for File
  29. if($file)
  30. {
  31.     if($server) # If -Server was passed we run check just against it and output to screen
  32.     {
  33.         $mfsrv = new-Object -com "MetaframeCOM.MetaframeServer"
  34.         $mfsrv.Initialize(6,$Server.ToUpper())
  35.         $mfsrv | foreach{"`n$($_.ServerName) `n$(’-'*20)";$_.PrinterDrivers| Format-Table} | out-File $file -enc ASCII
  36.     }
  37.     else # We run the check against the whole farm and output results to file
  38.     {
  39.         $mfarm = new-Object -com "MetaframeCOM.MetaframeFarm"
  40.         $mfarm.Initialize(1)
  41.         $mfarm.Servers | foreach{"`n$($_.ServerName) `n$(’-'*20)";$_.PrinterDrivers| Format-Table} | out-File $file -enc ASCII
  42.     }
  43. }
  44. else
  45. {
  46.     if($server) # If -Server was passed we run check just against it and output to screen
  47.     {
  48.         $mfsrv = new-Object -com "MetaframeCOM.MetaframeServer"
  49.         $mfsrv.Initialize(6,$Server.ToUpper())
  50.         $mfsrv | foreach{"`n$($_.ServerName) `n$(’-'*20)";$_.PrinterDrivers| Format-Table}
  51.     }
  52.     else # We run the check against the whole farm and output results to screen
  53.     {
  54.         $mfarm = new-Object -com "MetaframeCOM.MetaframeFarm"
  55.         $mfarm.Initialize(1)
  56.         $mfarm.Servers | foreach{"`n$($_.ServerName) `n$(’-'*20)";$_.PrinterDrivers| Format-Table}
  57.     }
  58.  }

Set-CitrixPSVersion.ps1 (Citrix Top 10)

I am constantly looking for content for my blog and tasks that can be automated. I believe this helps me learn and helps other with their needs.

In this search I was directed to convert/write Powershell examples for scripts located here http://community.citrix.com/display/cdn/Script+Exchange

Here is the first of those scripts.

Name: Set-CitrixPSVersion.ps1
Purpose: Sets PS Version on a Server, List of Servers, or all Servers in the Farm

  1. # Set-CitrixPSVersion.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Sets PS Version on a Server, List of Servers, or all Servers in the Farm
  5. Param($file,$server,$PSVer,[switch]$help,[switch]$all,[switch]$whatif,[switch]$show,[switch]$verbose)
  6. function HelpMe{
  7.     Write-Host
  8.     Write-Host " Set-CitrixPSVersion.ps1:" -fore Green
  9.     Write-Host "   Sets PS Version on a Server, List of Servers, or all Servers in the Farm"
  10.     Write-Host
  11.     Write-Host " Parameters:" -fore Green
  12.     Write-Host "   -File <fileName>       : Optional. Name of the File of Servers"
  13.     Write-Host "   -Server <serverName>   : Optional. Name of the Server to Change"
  14.     Write-Host "   -Verbose               : Optional. Enables Verbose Output"
  15.     Write-Host "   -All                   : Optional. Sets Version on all Servers [Requires -Server]"
  16.     Write-Host "   -Show                  : Optional. Displays the Version for Server(s)"
  17.     Write-Host "   -Help                  : Optional. Displays This"
  18.     Write-Host "   -Whatif                : Optional. Will not Commit Info just Display what would change"
  19.     Write-Host
  20.     Write-Host " Examples:" -fore Green
  21.     Write-Host "   Set PS Version on Server1 to STD" -fore White
  22.     Write-Host "     .\Set-CitrixPSVersion.ps1 -Server Server1 -psver STD " -fore Yellow
  23.     Write-Host
  24.     Write-Host "   Set PS Version on Servers in a File" -fore White
  25.     Write-Host "     .\Set-CitrixPSVersion.ps1 -file c:\Mylist.txt -psver STD " -fore Yellow
  26.     Write-Host
  27.     Write-Host "   Get PS Version from ALL server" -fore White
  28.     Write-Host "     .\Set-CitrixPSVersion.ps1 -all -Server myzdcserver" -fore Yellow
  29.     Write-Host
  30.     Write-Host " Product Edition Options" -fore Green
  31.     Write-Host "   STD = Citrix Presentation Server Standard Edition" -fore White
  32.     Write-Host "   ADV = Citrix Presentation Server Advanced Edition" -fore White
  33.     Write-Host "   ENT = Citrix Presentation Server Enterprise Edition" -fore White
  34.     Write-Host "   PLT = Citrix Presentation Server Platinum Edition" -fore White
  35.     Write-Host
  36. }
  37. function Ping-Server {
  38.     Param($srv)
  39.     $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
  40.     if($pingresult.statuscode -eq 0) {$true} else {$false}
  41. }
  42. function Set-PSVer{
  43.     Param($srv)
  44.     Write-Verbose "  Getting Citrix Server Object"
  45.     $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$srv)
  46.     $mfsrv = [system.Activator]::CreateInstance($type)
  47.     $mfsrv.Initialize(6,$srv)
  48.     if(!$?){Write-Host "   - Server [$srv] threw and Error" -fore red;return}
  49.  
  50.     if($show) # Check for $Show and will display only
  51.     {
  52.         Write-Host "   - PS Version for [$srv]: $($mfsrv.WinServerObject.mpsedition)"
  53.         return
  54.     }
  55.     else
  56.     {
  57.         if($whatif) # Check for $Whatif
  58.         {
  59.             Write-Host "  What if: Performing operation `"Set PS Version [$PSVer]`" on Target `"$srv`"."
  60.         }
  61.         else
  62.         {
  63.             Write-Verbose "   - Setting PSVer to [$PSVer] on [$srv]"
  64.             $mfsrv.WinServerObject.mpsedition = $PSVer
  65.             Write-Host "   - PS Version for [$srv]: $($mfsrv.WinServerObject.mpsedition)"
  66.         }
  67.     }
  68. }
  69. function Set-PSALL{
  70.     $mfarm = new-Object -com "MetaframeCOM.MetaframeFarm"
  71.     $mfarm.Initialize(1)
  72.     foreach($mfsrv in $mfarm.Servers)
  73.     {
  74.         if($show){Write-Host "   - PS Version for [$($mfsrv.ServerName)]: $($mfsrv.WinServerObject.MPSEdition)";continue}
  75.         if($whatif){Write-Host "  What if: Performing operation `"Set PS Version [$PSVer]`" on Target `"$($mfsrv.ServerName)`"."}
  76.         else
  77.         {
  78.             Write-Verbose "   - Setting PSVer to [$PSVer] on [$($mfsrv.ServerName)]"
  79.             $mfsrv.WinServerObject.mpsedition = $PSVer
  80.             Write-Host "   - PS Version for [$($mfsrv.ServerName)]: $($mfsrv.WinServerObject.MPSEdition)"
  81.         }
  82.     }
  83. }
  84.  
  85. # Script Setup. Checking Parameters
  86. Write-Host
  87.  
  88. ## Checing Verbose flag
  89. if($verbose){$verbosepreference = "Continue"}else{$erroractionpreference = "SilentlyContinue"}
  90.  
  91. ## Verifying that File/Server was passed. If not or -help I Call HelpMe and close.
  92. if(!$file -and !$server -and !$all -or $help){HelpMe;Return}
  93.  
  94. ## Verify Valid Edition was Passed
  95. if(!$show -and ($PSVer -notmatch "STD|ADV|ENT|PLT"))
  96. {
  97.     Write-Host " PS Edition [$PSVER] is NOT Valid. Please use STD, ADV, ENT, or PLT" -fore RED
  98.     Write-Host
  99.     Return
  100. }
  101.  
  102. # If $Server and we can ping it we run Set-PSVer against Server
  103. if($server -and (ping-server $server))
  104. {
  105.     Set-PSVer $server
  106.     Write-host
  107. }
  108.  
  109. # Check for -File and Verify the file is valid
  110. if($File -and (test-Path $file))
  111. {
  112.     Write-Verbose " - Processing File [$file]"
  113.     # Process each Server checking for blanks
  114.     foreach($Server in (get-Content $file | where{$_ -match "^\S"}))
  115.     {
  116.         Write-Host " + Processing Server [$Server]"
  117.         if(ping-Server $server){Set-PSVer $Server}else{Write-Host "   - Ping Failed to [$Server]" -fore Red}
  118.         Write-host
  119.     }
  120. }
  121.  
  122. if($all){Set-PSALL}
  123.  
  124. Write-Host

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}}}

Calculated Properties

nicad49 asked
“Can you explain the last part of each script? Specifically the @{n=’Servers’;…}etc. I understand that we are creating some sort
of an array, but if I run it as is, I don’t get any values returned.”

This is great question. I see examples like this used all the time without explanation. Unless you had the chance to review all the
help files from all the different CMDLets there is good chance you missed some REALLY cool stuff. This is one of those things
hidden in the bowels of Help files and Books.

Before I go into my specific example, lets look at this excerp from http://technet.microsoft.com/en-us/library/bb978655.aspx

“You can also use Select-Object to add calculated properties to an object. To add a calculated property, use the Property
parameter and type a hash table for the parameter value. Create an Expression key in the hash table and assign to the key an
expression that calculates a value. The hash table can also have a Name key.”

Basically what we can do is add a property to an object that is calculated. This calculation could be from other properties or just a
change to the way the data is configured. Here is how you use this feature @{name=”SomeName”;expression={”Script block used
to provide value(s)”}}.

Some important things to note:
* The Name can be anything, even a valid property name.
* Expression is a scriptblock. What does this mean? It means that you can put any code you want in there… even an entire script
and what ever is returned is put in the property you created.

Now that we have some foundation lets look back at my code. Here is the first example.

  1. $MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
  2. $MF.Initialize(1)
  3. $MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
  4.   @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  5.   @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType

Now the first problem maybe cut/paste not working correct so make sure line three is all one big long line.

Next lets zoom in on the Important line… number three

  1. $MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
  2.   @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  3.   @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType

First we have the $MF.Servers. This returns an array of Citrix Server Objects with all sorts of properties. We only really care about
the Applications and the Server name so we go to the next step.

Select-Object ServerName -expand Applications. This code takes each one of the MFCom Server objects and Strips out everything
but the ServerName and the Applications. The reason for -expand is because Applications is an array of MFCom Application
Objects and we want them all.

Here is the biggy
Select-Object ServerName,AppName,DistinguishedName,@{n=’Users’;e={$_.Users | %{$_.UserName}}},@{n=’Groups’;e={$_.Groups | %{$_.GroupName}}}
This section takes the object we Created with the ServerName and the Applications and Passes on the ServerName, but extracts
information about the applications we want to know like AppName and DistinguishedName. The problem is that we want the users
and groups to. The problem with this is these properties are (like Server and Application) arrays of MFCom User or MFCom Group
objects. Enter calculated fields.

Lets look each calculated field
@{n=’Users’;e={$_.Users | %{$_.UserName}}}
This basically says create a property named Users and add the values from $_.Users, but only give me the UserName… not all the
other properties.
@{n=’Groups’;e={$_.Groups | %{$_.GroupName}}}
Same thing except for with Groups.

Perhaps multilevel Citrix Nesting isn’t the best place to get your teeth wet on calculated properties, but I hope I cleared it up a bit
for you.

An Interactive Case for Powershell (Yet more Citrix Fun!)

I was recently in a discussion on Brian Madden Forums about listing Citrix Information and exporting to CSV. It seemed like a
perfect fit for Powershell so I converted the VBScripts to Powershell (of course taking an 85 line script to 3 lines. Convert is
hardly the correct term.)

Here is the Forum Topic
http://www.brianmadden.com/Forum/Topic/95285

Here is my Code. There were three scripts, so I made three as well.

  1. # Apps by Server CTXApps_by_Server_w_Users
  2. $MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
  3. $MF.Initialize(1)
  4. $MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
  5.        @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  6.        @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType
  1. # Apps with Servers
  2. $MF = New-Object -com MetaFrameCOM.MetaFrameFarm
  3. $MF.Initialize(1)
  4. $MF.Applications | Select-Object AppName,DistinguishedName,
  5.       @{n="Servers";e={$_.Servers | foreach{$_.ServerName}}} | export-Csv C:\AppsWithServer.Csv -noType
  1. # Apps with Servers and Users CTXApps_w_Servers_w_Users
  2. $MF = New-Object -com MetaFrameCOM.MetaFrameFarm
  3. $MF.Initialize(1)
  4. $MF.Applications | Select-Object AppName,DistinguishedName,
  5.       @{n="Servers";e={$_.Servers | foreach{$_.ServerName}}},
  6.       @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
  7.       @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsWithServerandUsers.Csv -noType

If you notice in my three scripts they all start with the same two lines. Effectively these are one liners that could be used
interactively. I think this does a great job of showing Citrix Admins how nicely Powershell will fit in to their daily lives.
Things that use to take 100s of lines of script writing can now be done interactively at a shell.

Get-CitrixServerLoad (The power of objects in Citrix)

I watch the forums at BrianMadden.com because I use Powershell a lot for Citrix. This question was brought up.

Q: How could one:
- query “server load” on all servers part of the farm
- extract all server under a minimum server load
- apply an “Offline” load evaluator on the extracted servers (in order to make them unavailable on the farm)

I posted a script to do what they wanted, but then I got to thinking… while it did achieve the goal it wasn’t very Powershellish.

As I have said over and over. The glory of Powershell is the objects. So I decided to Post this entry showing what I would consider the Powershell way :)
Ideally you should just do this at the prompt

PS> Get-CitrixServers | where{$_.WinServerObject.Serverload -lt $load} | Set-CitrixLoadEvalutor “OffLine”

This is easy to achieve with the following scripts or even better make them functions!

Get-CitrixServers

  1. param($Server)
  2. $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
  3. $mfarm = [system.Activator]::CreateInstance($type)
  4. $mfarm.Initialize(1)
  5. $mfarm.zones | foreach-Object{$_.OnlineServers}

Set-CitrixLoadEvalutor

  1. Param($server,$LoadEvaluator = "MFDefaultLE",[switch]$Verbose)
  2. #NOTE: This only work for 4.0 and 4.5
  3. if($verbose){$verbosepreference = "Continue"}
  4.  
  5. function Set-LE{
  6.     Param($mySrv)
  7.     # Getting Current LE
  8.     write-Verbose "   + Set-LE called : $($mySrv.ServerName)"
  9.     $le = $mfServer.AttachedLE
  10.     $le.LoadData(1)
  11.     Write-Verbose "     - Old Evaluator: $($le.LEName)"
  12.     Write-Verbose "     - Setting to $LoadEvaluator"
  13.  
  14.     # Assigning New LE
  15.     $mySrv.AttachLEByName($LoadEvaluator)
  16.  
  17.     # Checking LE
  18.     $le = $mySrv.AttachedLE
  19.     $le.LoadData(1)
  20.     Write-Verbose "     - Load Evaluator Set to $($le.LEName)"
  21.  
  22. }
  23.  
  24. if($Server)
  25. {
  26.     # Loading Server Object
  27.     Write-Verbose " + Processing $Server"
  28.     $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
  29.     $mfServer = [system.Activator]::CreateInstance($type)
  30.     $mfServer.Initialize(6,$Server)
  31.     Write-Verbose "   - Calling Set-LE"
  32.     Set-LE $mfServer
  33. }
  34.  
  35. if($list)
  36. {
  37.     foreach($Srv in (Get-Content $list))
  38.     {
  39.         Write-Verbose " + Processing $Srv"
  40.         # Loading Server Object
  41.         Write-Verbose "   - Getting Citrix Object"
  42.         $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Srv)
  43.         $mfServer = [system.Activator]::CreateInstance($type)
  44.         $mfServer.Initialize(6,$Srv)
  45.         Write-Verbose "   - Calling Set-LE"
  46.         Set-LE $mfServer
  47.     }
  48. }
  49.  
  50. if($input)
  51. {
  52.     foreach($Srv in $input)
  53.     {
  54.         Write-Verbose     " + Processing $Srv"
  55.         if($Srv.ServerName)
  56.         {
  57.             Write-Verbose "   - Input is a Citrix Server: $Srv"
  58.             Write-Verbose "   - Calling Set-LE"
  59.             Set-LE $Srv
  60.         }
  61.         else
  62.         {