Recently I was flicking through the “commonparamters” topic in Powershell when I stumbled upon a rather obscure parameter called ‘Outvariable’. Upon careful analysis, I understood that this is indeed a chance encounter with something that would prove to be useful, strange and mysterious.
So, the main purpose of Outvariable is to save the output of a command in a variable before its dispatched over the pipeline.
At first glance this might not even seem a big deal. Don’t we use variables for the same purpose in Powershell?
Indeed, we do. However, this is where things start getting intriguing. Let me explain.
#We have been taught to save outputs in variables as follows:
$my_variable = Get-Service dhcp
Now to display the contents of this variable we must run the variable name in shell:
$my_variable
Status Name DisplayName
------ ---- -----------
Running dhcp DHCP Client
#This is where the outvariable parameter could be of service. Following is the way to use it:
Get-Service dhcp -OutVariable network
Status Name DisplayName
------ ---- -----------
Running dhcp DHCP Client
In this example we ran the same Get-Service command. However, we have not added the output to a variable directly. Instead we utilized the ‘outvariable’ parameter to state that the output must be stored in a variable by the name ‘network’.
We need not use the ‘$’ symbol for the variable here.
The other vital point here is that the content of the variable was displayed without us having to run the variable name explicitly. So, in effect, we stored the output of the command in the variable and also showcased the output.
#Running the variable name confirms our finding.
$network
Status Name DisplayName
------ ---- -----------
Running dhcp DHCP Client
#Now that’s an interesting thing. However, we can also add more objects to the variable using the + symbol as follows:
Get-Service device* -OutVariable +network
Status Name DisplayName
------ ---- -----------
Stopped DeviceAssociati... DeviceAssociationBroker_9d940
Running DeviceAssociati... Device Association Service
Stopped DeviceInstall Device Install Service
Stopped DevicePickerUse... DevicePicker_9d940
Running DevicesFlowUser... DevicesFlow_9d940
$network
Status Name DisplayName
------ ---- -----------
Running dhcp DHCP Client
Stopped DeviceAssociati... DeviceAssociationBroker_9d940
Running DeviceAssociati... Device Association Service
Stopped DeviceInstall Device Install Service
Stopped DevicePickerUse... DevicePicker_9d940
Running DevicesFlowUser... DevicesFlow_9d940
Here, we run the ‘Get-Service’ command to find the services which have names staring with the letter ‘s’. The ‘outvariable’ parameter is mentioned before the pipeline. After the pipeline, a ‘where’ commandlet is used further filter the list to just the services that are running.
Get-Service -Name s* -OutVariable firstpart | where {$_.Status -eq "running"}
Output
----------
Status Name DisplayName
------ ---- -----------
Running SamSs Security Accounts Manager
Running SCardSvr Smart Card
Running Schedule Task Scheduler
Running SecurityHealthS... Windows Security Service
Running SENS System Event Notification Service
Running Sense Windows Defender Advanced Threat Pr...
Running SessionEnv Remote Desktop Configuration
Running SgrmBroker System Guard Runtime Monitor Broker
Running ShellHWDetection Shell Hardware Detection
Running SnowInventoryAg... Snow Inventory Agent
Running Spooler Print Spooler
Running sppsvc Software Protection
Running SSDPSRV SSDP Discovery
Running SstpSvc Secure Socket Tunneling Protocol Se...
Running StateRepository State Repository Service
Running StorSvc Storage Service
Running SysMain SysMain
Running SystemEventsBroker System Events Broker
Now let’s check contents of the ‘$firstpart’ variable. As we discussed earlier, the ‘outvariable’ parameter ensures that the objects are saved in it before being passed over the pipeline. In our example we applied another filter to include only those services which were in ‘running’ state. Hence, the variable ‘$firstpart’ should contain all the objects, even those which got filtered in the later of our expression.
$firstpart
Status Name DisplayName
------ ---- -----------
Running SamSs Security Accounts Manager
Running SCardSvr Smart Card
Stopped ScDeviceEnum Smart Card Device Enumeration Service
Running Schedule Task Scheduler
Stopped SCPolicySvc Smart Card Removal Policy
Stopped SDRSVC Windows Backup
Stopped seclogon Secondary Logon
Running SecurityHealthS... Windows Security Service
Stopped SEMgrSvc Payments and NFC/SE Manager
Running SENS System Event Notification Service
Running Sense Windows Defender Advanced Threat Pr...
Stopped SensorDataService Sensor Data Service
Stopped SensorService Sensor Service
Stopped SensrSvc Sensor Monitoring Service
Running SessionEnv Remote Desktop Configuration
Running SgrmBroker System Guard Runtime Monitor Broker
Stopped SharedAccess Internet Connection Sharing (ICS)
Stopped SharedRealitySvc Spatial Data Service
Running ShellHWDetection Shell Hardware Detection
Stopped shpamsvc Shared PC Account Manager
Stopped smphost Microsoft Storage Spaces SMP
Stopped SmsRouter Microsoft Windows SMS Router Service.
Stopped smstsmgr ConfigMgr Task Sequence Agent
Stopped SNMPTRAP SNMP Trap
Running SnowInventoryAg... Snow Inventory Agent
Stopped spectrum Windows Perception Service
Running Spooler Print Spooler
Running sppsvc Software Protection
Running SSDPSRV SSDP Discovery
Running SstpSvc Secure Socket Tunneling Protocol Se...
Running StateRepository State Repository Service
Stopped stisvc Windows Image Acquisition (WIA)
Running StorSvc Storage Service
Stopped svsvc Spot Verifier
Stopped swprv Microsoft Software Shadow Copy Prov...
Running SysMain SysMain
Running SystemEventsBroker System Events Broker
#Let’s check the count of the $firstpart variable and then we shall compare that to the final output.
$firstpart.Count
37
(Get-Service -Name s* -OutVariable firstpart | where {$_.Status -eq "running"}).count
19
As seen above, the objects in $firstpart variable are 37 and the entire command’s output count is 19. This confirms the behaviour of ‘outvariable’ as a parameter that saves the objects before its further filtered or actioned once its passed over the pipeline.
This is all good; however, the question that still lingered in my mind is, how do I use this in any of my daily tasks. The obvious advantage of ‘outvariable’ is that we can save outputs to be used later in the script. Let’s delve deeper into this conundrum.
The Debugging Angle
On a server, we want to find the disk name, disk space, free percentage and drive type. We have a Powershell expression:
Get-CimInstance -Class CIM_LogicalDisk | Select-Object @{Name="Size(GB)";Expression={$_.size/1gb}}, @{Name="Free Space(GB)";Expression={$_.freespace/1gb}}, @{Name="Free (%)";Expression={"{0,6:P0}" -f(($_.freespace/1gb) / ($_.size/1gb))}}, DeviceName, DriveType | Where-Object DriveType -EQ '3'
This expression falters in one aspect. It fails to display the “DeviceName”, meaning the drive name in the output. We will have to do some debugging here to get to the bottom of this.
Let’s use the ‘outvariable’ to check the objects being stored at each stage. This will give us a fair idea of the sections of the expression that are working fine.
The variable $first didn’t show any issues.
$second did show the ‘DeviceName’ field as blank. So this is where the chain was broken. Next, lets further drill down to the first drive values.
#Retrieving the first index in the array.
$second[0]
Size(GB) : 99.4462852478027
Free Space(GB) : 50.0025291442871
Free (%) : 50 %
DeviceName :
DriveType : 3
#Confirming if devicename is indeed blank.
second[0].DeviceName -eq $null
True
Its clear that this property is incorrect. These properties are being picked up from the first part of the expression. Lets find out the correct name.
#To check the available properties
$first | Get-member
The output of this shows that the correct property name is ‘deviceid’. Lets replace ‘devicename’ with ‘deviceid’ in our expression.
Get-CimInstance -Class CIM_LogicalDisk -OutVariable first| Select-Object @{Name="Size(GB)";Expression={$_.size/1gb}}, @{Name="Free Space(GB)";Expression={$_.freespace/1gb}}, @{Name="Free (%)";Expression={"{0,6:P0}" -f(($_.freespace/1gb) / ($_.size/1gb))}}, DeviceID, DriveType -OutVariable second | Where-Object DriveType -EQ '3' -OutVariable third | ft "deviceid", "devicetype", "Size(GB)", "Free Space(GB)", "Free (%)"
DeviceID devicetype Size(GB) Free Space(GB) Free (%)
-------- ---------- -------- -------------- --------
C: 99.4462852478027 50.0522575378418 50 %
D: 499.872985839844 378.308654785156 76 %
X: 49.9980430603027 17.8431549072266 36 %
The output now fulfills our requirement.
The ‘outvariable’ parameters aren’t needed anymore, so they can be removed. This is an example of using the ‘outvariable’ expression effectively to debug.
The Plot Thickens
I did spend a considerable time researching on the issues I encountered in ‘outvariable’. I must admit this led me to the recesses of the Powershell community ?.
The ‘outvariable’ parameter creates a variable of the type ‘array’. This gets enforced on the variable, come what may. I had to run a few tests of my own to come to terms with this abnormality.
Get-Date is the command used to check the date and its always of the type ‘system.datetime’.
$date_var = Get-Date; $date_var.GetType().FullName
System.DateTime
#Let’s do the same using the ‘outvariable’ and check the result.
Get-Date -OutVariable ovdate; $ovdate.GetType().fullname
Sunday, July 5, 2020 7:54:02 AM
System.Collections.ArrayList
This clearly affirms the anomaly. There have been lengthy discussions about this in Github. Though this has been recognized as an issue, the general consensus there is to let it be as is. The rationale behind this is that ‘outvariable’ is being used in countless production environments; hence removing it would cause a lot of unwanted issue. The fact that this concept has been as is since Powershell 1.0 only strengthens this argument.
In conclusion, the ‘outvariable’ parameter must be utilized when the need is to save the objects, so that they can be used later on in the script.
It can also be used in certain debugging scenarios where output from one part of the script is passed over pipelines.
Its necessary to know the limitations of this parameter.
There is another aspect of ‘outvariable’ in relation to Exchange and Office 365. I will cover that in the next post.
Cheers!