Powershell Black Magic


If like us you’ve done some programming with Powershell, you know there are some gotchas in the language. If you haven’t yet met them – you’re going to enjoy this one.

We encountered the most recent gotcha when trying to sort a list of IP addresses according to “distance” from the local machine’s IP addresses. The idea is the following:

  • Let’s call the original list A
  • Get the list of local IP addresses (B)
  • For each pair (a from A, b from B), compute the “distance between them”. We will approximate this by counting how many bytes (starting from the MSB) are identical.
  • For each address in A, compute the maximal match length.
  • Sort A according to the maximal match length, descending.

This algorithm is not 100% accurate, but is good enough for our purposes.

To get the list of local IP addresses – we used the following line:

$ownIPs = (Get-NetIPAddress | ?{ $_.AddressFamily -eq "IPv4" -and !($_.IPAddress -match "169") -and !($_.IPaddress -match "127") }).IPAddress

(See for example https://powershell.faboit.com/2017/08/how-do-i-find-my-ip-address-in.html )

This worked… Most of the time. Our code that used $ownIPs failed on machines with only one IP address. Why?

Well, it is “well known” in Powershell that functions that return arrays of length one will actually return the first object of the array. Unless you wrap them in @(…) that is. So to correct this issue you would need the following line:

$ownIPs = @((Get-NetIPAddress | ?{ $_.AddressFamily -eq "IPv4" -and !($_.IPAddress -match "169") -and !($_.IPaddress -match "127") }).IPAddress)

This was encountered enough times, that in our internal wiki we have a page dedicated to Powershell, called “Powershell black magic”, originally written by Ofer Mustigman. For the benefit of other poor souls who need to write or maintain Powershell code, here are the contents of that page:

  • If a list has 1 item in it, it will be treated as that item. It means that if it doesn’t have the .Length property (like numbers don’t) trying to asses the lists length will result in an error, and if its a string you’ll get the length of the string. This is avoidable by wrapping the list in a list like so:
  • In Powershell 2, If you pass a hash table or a list into a job, an exception will be thrown when you access it for the second time. The solution is to copy it like so:
$inputObject = @($input)[0]
  • The reason we do that is that this way it just copies the entire thing and doesn’t access it one part at a time.
  • In Windows 10, If you use a variable from the file scope inside a function in an exported lib it will not find it. The solution is to copy it into the lib during its initialization.
  • When you use the Start-Job, Wait-Job or Receive-job in a function, No matter what you return from that function the function will return an array of all the jobs used in that function and your return value as the last entry. This is intended by Microsoft, its a feature of Powershell. Avoid by creating another function that calls the job and do:
$myvalue = (JobFunction)[1] 
  • When you load a DLL file in a process you can’t delete it inside that process. Whenever you load a DLL inside anything that runs in the main thread of our Powershell, use Start-Job to load it.

Normally I would close with “have fun coding”, but this is Powershell, so I’ll just wish you a less painful debugging session 🙂