The Curious Case of the OutVariable Parameter

The Curious Case of the OutVariable Parameter

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

Oh snap!

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!