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

Set-CitrixServerLogon.ps1 (Citrix Top 10)

Here is a useful little script. This Creates a MFCom Server Object and disables or Enables Logons for that Server.

  1. # Set-CitrixServerLogon.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Sets the Server to Enable or Disable Logons
  5. Param($Server,[switch]$enable,[switch]$disable,[switch]$help)
  6. function HelpMe{
  7.     Write-Host
  8.     Write-Host " Set-CitrixServerLogon.ps1:" -fore Green
  9.     Write-Host "   Sets the Server to Enable or Disable Logons"
  10.     Write-Host
  11.     Write-Host " Parameters:" -fore Green
  12.     Write-Host "   -Server                  : Optional. Server to Set Logon"
  13.     Write-Host "   -Enable                  : Optional. Checks Hours of Idle Time (Default)"
  14.     Write-Host "   -Disable                 : Optional. Checks Minutes of Idle Time"
  15.     Write-Host "   -Help                    : Optional. Displays This"
  16.     Write-Host
  17.     Write-Host " Examples:" -fore Green
  18.     Write-Host "   To disable the Logon for a Server" -fore White
  19.     Write-Host "     Set-CitrixServerLogon.ps1 -server <serverName> -Disable" -fore Yellow
  20.     Write-Host
  21. }
  22.  
  23. if(!$Server -or $help){helpme;Write-Host;return}
  24.  
  25. Write-Host
  26.  
  27. Write-Host " Getting Server [$Server]"
  28. $mfsrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
  29.  
  30. Write-Host " - Initializing Server"
  31. $mfsrv.Initialize(6,$Server)
  32.  
  33. if($enable)
  34. {
  35.     Write-Host " - Setting to EnableLogon = 1"
  36.     $mfSrv.WinServerObject.EnableLogon = 1
  37. }
  38. if($disable)
  39. {
  40.     Write-Host " - Setting to EnableLogon = 0"
  41.     $mfSrv.WinServerObject.EnableLogon = 0
  42. }
  43.  
  44. Write-Host " - Server [$($mfSrv.ServerName)] is set to [$($mfSrv.WinServerObject.EnableLogon)] for EnableLogon"
  45.  
  46. Write-Host

Another option would be to remove the Apps from the Server all together.

  1. # Unpublish-CitrixServer.ps1
  2. # Brandon Shell [MVP]
  3. # www.bsonposh.com
  4. # Removes all App from Server
  5. Param($Server)
  6. $mfsrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
  7. $mfsrv.Initialize(6,$Server.ToUpper())
  8. $mfsrv | foreach{$_.Applications} | foreach{$_.LoadData(1);$_.RemoveServer($Server.ToUpper());$_.SaveData()}

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

Citrix Load Evaluators

I have had a few request for how to deal with Citrix Load Evaluators. There are few gotchas, but it is fairly strait forward.

There are two Built-in Citrix Load Evaluators Default and Advanced.. the problem is that from MFCom, this is not what they are called. This is what you should use.
- Default = MFDefaultLE
- Advanced = LMSDefaultLE

  1. function Set-CitrixLoadEvaluator{
  2.     Param($server = $(throw ‘$Server is Required’),$LoadEvaluator = "MFDefaultLE")
  3.  
  4.     # Loading Server Object
  5.     $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
  6.     $mfServer = [system.Activator]::CreateInstance($type)
  7.     $mfServer.Initialize(6,$Server)
  8.  
  9.     # Getting Current LE
  10.     $le = $mfServer.AttachedLE
  11.     $le.LoadData(1)
  12.     Write-Host "Old Evaluator: $($le.LEName)"
  13.     Write-Host "Setting Load Evaluator on $server to $LoadEvaluator"
  14.  
  15.     # Assigning New LE
  16.     $mfServer.AttachLEByName($LoadEvaluator)
  17.  
  18.     # Checking LE
  19.     $le = $mfServer.AttachedLE
  20.     $le.LoadData(1)
  21.     Write-Host "Load Evaluator Set to $($le.LEName)"
  22. }

Run-Command.ps1 : Run External Commands with Power!

I was working late tonight and we had to run a bunch of third party EXEs and such. We do this a good bit so I can’t always avoid calling external executables and I also find psexec.exe much easier than any powershell way to run remote commands. That said I find myself constantly writing this.

  1. $servers = Get-Content $file
  2. foreach($server in $servers)
  3. {
  4.    Do Something Here Like
  5.    psexec \\$server mycmd.exe param1
  6. }

I decided to write a script call Run-Command.ps1.
This will take three parameters
- File (list of servers to process)
- Cmd (Command to run with %S% where you want the server name to be replaced)
- Check (just shows what command would run)
- Will also take Piped Input

Example:
PS> .\Run-Command.ps1 -file c:\serverlist.txt -cmd “psexec \\%S% mycmd.exe Hello World” -check

Run-Command.ps1

  1. Param($file,$cmd,[switch]$whatif,[switch]$verbose)
  2. Begin{
  3.     function Ping-Server {
  4.         Param([string]$srv)
  5.         $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
  6.         if($pingresult.statuscode -eq 0) {$true} else {$false}
  7.     }
  8.     $servers = @()
  9. }
  10. Process{
  11.     if($_)
  12.     {
  13.         if($_.ServerName){$servers += $_.ServerName}
  14.         else{$servers += $_}
  15.     }
  16. }
  17. End{
  18.     if($file){Get-Content $file | %{$servers += $_}}
  19.     foreach($server in $servers)
  20.     {
  21.         if(ping-server $server)
  22.         {
  23.             if($verbose){Write-Host "+ Processing Server $Server"}
  24.             $mycmd = $cmd -replace "\%S\%",$Server
  25.             if($whatif){Write-Host "  - WOULD RUN $mycmd"}
  26.             else{if($verbose){Write-Host "  - Running $mycmd"};invoke-Expression $mycmd}
  27.         }
  28.         else
  29.         {
  30.             Write-Host "+ $Server FAILED PING" -foregroundcolor RED
  31.         }
  32.     }
  33. }

Get-Uptime (fun with Write-Host)

I am a BIG fan of Write-Host. Think of Write-Host like wscript.Echo (vbscript) or echo (cmd.exe,) on steroids. I know I know… you want to know what I think is the best feature and funny enough… its COLOR!!! Yes, COLOR, no more two color schemes. With Write-Host you can do all sorts of colors with foreground and background.

To celebrate, I wanted to write a post illustrating just how wonderful write-host can be and I thought it would be best to use a script that I wrote. This is a script to get the uptime of server.

  1. Function Get-Uptime {
  2.     Param([string]$server)
  3.     Begin {
  4.         function Uptime {
  5.             param([string]$srv)
  6.             $os = Get-WmiObject Win32_OperatingSystem -ComputerName $srv
  7.             $uptime = $os.LastBootUpTime
  8.             return $uptime
  9.         }
  10.         function ConvertDate {
  11.             param([string]$date)
  12.             $year = $date.substring(0,4)
  13.             $Month = $date.Substring(4,2)
  14.             $Day = $date.Substring(6,2)
  15.             $hour = $date.Substring(8,2)
  16.             $min = $date.Substring(10,2)
  17.             $sec = $date.Substring(12,2)
  18.             $RebootTime = new-Object System.DateTime($year,$month,$day,$hour,$min,$sec)
  19.             $now = [System.DateTime]::Now
  20.             $uptime = $now.Subtract($RebootTime)
  21.             Write-Host "==> Uptime: " -NoNewLine
  22.             Write-Host "$($uptime.days)" -NoNewLine -ForeGroundColor Green
  23.             Write-Host " days, " -NoNewLine
  24.             Write-Host "$($uptime.Hours)" -NoNewLine -ForeGroundColor Green
  25.             Write-Host " hours, " -NoNewLine
  26.             Write-Host "$($uptime.Minutes)" -NoNewLine -ForeGroundColor Green
  27.             Write-Host " minutes, " -NoNewLine
  28.             Write-Host "$($uptime.seconds)" -NoNewLine -ForeGroundColor Green
  29.             Write-Host " seconds, "
  30.         }
  31.         Write-Host
  32.         $process = @()
  33.     }
  34.     Process {
  35.         if($_){
  36.             if($_.ServerName ){
  37.                 $process += $_.ServerName
  38.             }
  39.             else{
  40.                 $process += $_
  41.             }
  42.         }
  43.     }
  44.     End {
  45.         if($Server){$process += $server}
  46.         foreach ($Server in $process){
  47.             $result = uptime $server
  48.             Write-Host "Uptime for Server [" -NoNewline
  49.             Write-Host $server  -NoNewline -foregroundcolor Cyan
  50.             Write-Host "]"
  51.             ConvertDate $result
  52.             Write-Host
  53.         }
  54.         Write-Host
  55.     }
  56. }

As you can see I use Write-Host numerous times so let me explain a couple of the lines so you can see what is happening.

  1. Write-Host "==> Uptime: " -NoNewLine
  2. Write-Host "$($uptime.days)" -NoNewLine -ForeGroundColor Green
  3. Write-Host " days, " -NoNewLine
  4. Write-Host "$($uptime.Hours)" -NoNewLine -ForeGroundColor Green
  5. Write-Host " hours, " -NoNewLine
  6. Write-Host "$($uptime.Minutes)" -NoNewLine -ForeGroundColor Green
  7. Write-Host " minutes, " -NoNewLine
  8. Write-Host "$($uptime.seconds)" -NoNewLine -ForeGroundColor Green
  9. Write-Host " seconds, "

What I wanted was to have the output look like uptime.exe, but I wanted to add a little flare so I decided highlighting the numbers in a different color would make it easier to read.

There are two switches that I use here -NoNewLine and -ForeGroundColor. These are pretty self explanatory, but I will explain anyway.
-NoNewLine: This tells Write-Host not to add a New Line at the end of the string.

-ForeGroundColor: This tells you the color you want the text to display in (Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White.)

There are few other useful switches I didn’t use.
-BackGroundColor: Same available colors as ForeGroundColor, but this applies to the background color. So if you want the text to be Green on Black you would do
[code]Write-Host “Hello World” -ForeGroundColor Green -BackGroundColor Black[/code]

-Separator: This applies only if you pass an array of strings to write-host. This will prepend the “separator” to each string.

  1. write-host ("hello","this","is","Separated","by","`"-`"") -Separator "-"

As you can see Write-Host is pretty useful, especially when you need your output to be nice and neat.

OH! Before I go… there is one thing to watch out for. Although for most people write-host behaves exactly as expected when you start working with PowerShell and start understanding Pipes its important to understand that Write-Host does NOT write to the pipeline. It writes to stdout. This is very important if you are trying to write stuff the pipe. To write to the pipe you need to use write-output.