Friday, August 31, 2012

One character drama


I work with several large brown field PowerShell projects, which were developed by different peoples, both amateurs and pro in PS. I saw different, ugly and beautiful things.

The most confusing are one char syntax, that powershell is rich of. Some of this are common, some of this are rare and even redundant.
Here is short listing:
  1. # Explicit provider  
  2. Get-Content -Path Function:prompt  
  3.   
  4. # Old good string format  
  5. 'This is my post about {1} number {0}' -F 3, 'powershell'  
  6.   
  7. # Escaping  
  8. "First line`r`nSecond one"  
  9.   
  10. # Explicit scope  
  11. $Global:test = 'GlobalTest'  
  12.   
  13. # Hashtable 
  14. $hash = @{ Name = 'Hashtable'; Description = 'Ordinary Collections.Hashtable'; }  
  15.   
  16. # Call operators  
  17. & 'Get-Process'  
  18. . 'Get-Process'  
  19.   
  20. # Use bool in switch argument  
  21. $someBool = $true  
  22. New-Item -Path Variable:SwitchTest -Value 1 -Verbose:$someBool  
  23.   
  24. # Simple scriptblock  
  25. $sb = { Get-Process }   
  26.   
  27. # Foreach-Object and Where-Object aliases  
  28. Get-Process | ?{ $_.PM -gt 1000 } | %{ $_.ProcessName }  

Item or array?

PowerShell cmdlets behave in interesting way. You can get an array or a single object from one method, so, be ready!
Innocent code snippet:

  1. $processes = Get-Process -Name powershell* -Verbose  
  2. If ($processes.count -gt 1) {  
  3.     "There are several powershell consoles"  
  4. } ElseIf ($processes.count -eq 1) {  
  5.     "There is one powershell console"  
  6. } Else {  
  7.     "There are no powershell consoles at all"  
  8. }  
What can go wrong?

But PowerShell runtime does a following thing:
  • If cmdlet returns several objects, it wraps them in object[] 
  • If only one object is returned to pipe, it returns the object itself (Process class instance in our case)
  • If no results are returned, it returns $null
You will get  "There are no powershell consoles at all" when you have both 0 or 1 console.

The preferred way to work with cmdlet output is pipeline, so most clean and robust solution will be:
  1. $powershellConsoles = 0  
  2. Get-Process -Name powershell* | ForEach-Object { $powershellConsoles++ }  
  3.   
  4. # First way, generic    
  5. If ($powershellConsoles -gt 1) {    
  6.     "There are several powershell consoles"    
  7. } ElseIf ($powershellConsoles -eq 1) {    
  8.     "There is one powershell console"    
  9. } Else {    
  10.     "There are no powershell consoles at all"    
  11. }  

Also, you can handle output like that:
  1. $processes = Get-Process -Name powershell*
  2.   
  3. # First way, generic  
  4. If ($processes -is [array]) {  
  5.     "There are several powershell consoles"  
  6. } ElseIf ($processes -ne $null) {  
  7.     "There is one powershell console"  
  8. } Else {  
  9.     "There are no powershell consoles at all"  
  10. }  
  11.   
  12. # Second way, straight typed  
  13. If ($processes -is [array]) {  
  14.     "There are several powershell consoles"  
  15. } ElseIf ($processes -is [System.Diagnostics.Process]) {  
  16.     "There is one powershell console"  
  17. } Else {  
  18.     "There are no powershell consoles at all"  
  19. }  

Also, notice, that default cmdlets behavior is to write error, if explicit conditions were determined:
  1. # Will silently return $null, if there are no jobs at all  
  2. Get-Job  
  3.   
  4. # Will return null and error message, if there are no jobs reaching conditions  
  5. Get-Job -Name NonexistableJob  

The very beginning

During my development studies, I found a lot of interesting and twisted stuff (sure thing, powershell is the leader!), which I want to share.