The PowerShell Iron Scripter: My solution to prequel puzzle 2
As I mentioned in my previous blog article, each week leading up to the PowerShell + DevOps Global Summit 2018, PowerShell.org will be posting an iron scripter prequel puzzle on their website. As their website states, think of the iron scripter as the successor to the scripting games.
If you haven't done so already, I recommend reading my solution to the Iron Scripter prequel puzzle 1 because some things are glossed over in this blog article that were covered in detail in that previous one.
Prequel puzzle 2 provides you with an older script that looks like it was written back in the VBScript days. It retrieves information about the operating system, memory, and logical disks. This entry is fairly similar to my previous one as it queries all of the remote computers at once using a CIM session which is created with the New-CimSession cmdlet that was introduced in PowerShell version 3.0. Using the CIM cmdlets instead of the older WMI ones allows it to run in PowerShell Core 6.0.
The built-in DriveType enumeration is used for parameter validation and tabbed expansion / intellisense of the DriveType parameter.
I also decided to add the operating system ReleaseId which has to be retrieved from the registry.
1#Requires -Version 3.0
2function Get-MrSystemInfo {
3
4<#
5.SYNOPSIS
6 Retrieves information about the operating system, memory, and logical disks from the specified system.
7
8.DESCRIPTION
9 Get-MrSystemInfo is an advanced function that retrieves information about the operating system, memory,
10 and logical disks from the specified system.
11
12.PARAMETER CimSession
13 Specifies the CIM session to use for this function. Enter a variable that contains the CIM session or a command that
14 creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see
15 about_CimSessions.
16
17.PARAMETER DriveType
18 Specifies the type of drive to query the information for. By default, all drive types are returned, but they can be
19 narrowed down to a specific type of drive such as only fixed disks. The parameter autocompletes based on the built-in
20 DriveType enumeration.
21
22.EXAMPLE
23 Get-MrSystemInfo
24
25.EXAMPLE
26 Get-MrSystemInfo -DriveType Fixed
27
28.EXAMPLE
29 Get-MrSystemInfo -CimSession (New-CimSession -ComputerName Server01, Server02)
30
31.EXAMPLE
32 Get-MrSystemInfo -DriveType Fixed -CimSession (New-CimSession -ComputerName Server01, Server02)
33
34.INPUTS
35 None
36
37.OUTPUTS
38 Mr.SystemInfo
39
40.NOTES
41 Author: Mike F Robbins
42 Website: http://mikefrobbins.com
43 Twitter: @mikefrobbins
44#>
45
46 [CmdletBinding()]
47 [OutputType('Mr.SystemInfo')]
48 param (
49 [Microsoft.Management.Infrastructure.CimSession[]]$CimSession,
50
51 [System.IO.DriveType]$DriveType
52 )
53
54 $Params = @{
55 ErrorAction = 'SilentlyContinue'
56 ErrorVariable = 'Problem'
57 }
58
59 if ($PSBoundParameters.CimSession) {
60 $Params.CimSession = $CimSession
61 }
62
63 $OSInfo = Get-CimInstance @Params -ClassName Win32_OperatingSystem -Property CSName, Caption, Version, ServicePackMajorVersion, ServicePackMinorVersion,
64 Manufacturer, WindowsDirectory, Locale, FreePhysicalMemory, TotalVirtualMemorySize, FreeVirtualMemory
65
66 $ReleaseId = Invoke-CimMethod @Params -Namespace root\cimv2 -ClassName StdRegProv -MethodName GetSTRINGvalue -Arguments @{
67 hDefKey=[uint32]2147483650; sSubKeyName='SOFTWARE\Microsoft\Windows NT\CurrentVersion'; sValueName='ReleaseId'}
68
69 if ($PSBoundParameters.DriveType) {
70 $Params.Filter = "DriveType = $($DriveType.value__)"
71 }
72
73 $LogicalDisk = Get-CimInstance @Params -ClassName Win32_LogicalDisk -Property SystemName, DeviceID, Description, Size, FreeSpace, Compressed
74
75 foreach ($OS in $OSInfo) {
76
77 foreach ($Disk in $LogicalDisk | Where-Object SystemName -eq $OS.CSName) {
78 if (-not $PSBoundParameters.CimSession) {
79 $ReleaseId.PSComputerName = $OS.CSName
80 }
81
82 [pscustomobject]@{
83 ComputerName = $OS.CSName
84 OSName = $OS.Caption
85 OSVersion = $OS.Version
86 ReleaseId = ($ReleaseId | Where-Object PSComputerName -eq $OS.CSName).sValue
87 ServicePackMajorVersion = $OS.ServicePackMajorVersion
88 ServicePackMinorVersion = $OS.ServicePackMinorVersion
89 OSManufacturer = $OS.Manufacturer
90 WindowsDirectory = $OS.WindowsDirectory
91 Locale = [int]"0x$($OS.Locale)"
92 AvailablePhysicalMemory = $OS.FreePhysicalMemory
93 TotalVirtualMemory = $OS.TotalVirtualMemorySize
94 AvailableVirtualMemory = $OS.FreeVirtualMemory
95 Drive = $Disk.DeviceID
96 DriveType = $Disk.Description
97 Size = $Disk.Size
98 FreeSpace = $Disk.FreeSpace
99 Compressed = $Disk.Compressed
100 PSTypeName = 'Mr.SystemInfo'
101 }
102
103 }
104
105 }
106
107 foreach ($p in $Problem) {
108 Write-Warning -Message "An error occurred on $($p.OriginInfo). $($p.Exception.Message)"
109 }
110
111}
The function shown in the previous example outputs the raw data returned by the cmdlets as a single type of object with a custom name. None of the disk or memory sizes are converted in case the person working with this function wants to use that raw information. The only exception is that locale is converted to decimal instead of returning it as the default hexadecimal value.
Who really wants to see drives or memory returned in bytes or kilobytes when they run a PowerShell
command? A types.ps1xml
file is used to extend the types of the returned object so the default
output is much more user friendly.
1<Types>
2 <Type>
3 <Name>Mr.SystemInfo</Name>
4 <Members>
5 <MemberSet>
6 <Name>PSStandardMembers</Name>
7 <Members>
8 <PropertySet>
9 <Name>DefaultDisplayPropertySet</Name>
10 <ReferencedProperties>
11 <Name>ComputerName</Name>
12 <Name>OSName</Name>
13 <Name>OSVersion</Name>
14 <Name>ReleaseId</Name>
15 <Name>ServicePack</Name>
16 <Name>OSManufacturer</Name>
17 <Name>WindowsDirectory</Name>
18 <Name>LocaleName</Name>
19 <Name>AvailableRAM(GB)</Name>
20 <Name>TotalVM(GB)</Name>
21 <Name>AvailableVM(GB)</Name>
22 <Name>Drive</Name>
23 <Name>DriveType</Name>
24 <Name>Size(GB)</Name>
25 <Name>FreeSpace(GB)</Name>
26 <Name>PercentUsed</Name>
27 <Name>Compressed</Name>
28 </ReferencedProperties>
29 </PropertySet>
30 </Members>
31 </MemberSet>
32 </Members>
33 </Type>
34 <Type>
35 <Name>Mr.SystemInfo</Name>
36 <Members>
37 <ScriptProperty>
38 <Name>ServicePack</Name>
39 <GetScriptBlock>
40 "$($this.ServicePackMajorVersion).$($this.ServicePackMinorVersion)"
41 </GetScriptBlock>
42 </ScriptProperty>
43 </Members>
44 </Type>
45 <Type>
46 <Name>Mr.SystemInfo</Name>
47 <Members>
48 <ScriptProperty>
49 <Name>LocaleName</Name>
50 <GetScriptBlock>
51 ([System.Globalization.CultureInfo]($this.Locale)).Name
52 </GetScriptBlock>
53 </ScriptProperty>
54 </Members>
55 </Type>
56 <Type>
57 <Name>Mr.SystemInfo</Name>
58 <Members>
59 <ScriptProperty>
60 <Name>AvailableRAM(GB)</Name>
61 <GetScriptBlock>
62 "{0:N2}" -f ($this.AvailablePhysicalMemory / 1MB)
63 </GetScriptBlock>
64 </ScriptProperty>
65 </Members>
66 </Type>
67 <Type>
68 <Name>Mr.SystemInfo</Name>
69 <Members>
70 <ScriptProperty>
71 <Name>TotalVM(GB)</Name>
72 <GetScriptBlock>
73 "{0:N2}" -f ($this.TotalVirtualMemory / 1MB)
74 </GetScriptBlock>
75 </ScriptProperty>
76 </Members>
77 </Type>
78 <Type>
79 <Name>Mr.SystemInfo</Name>
80 <Members>
81 <ScriptProperty>
82 <Name>AvailableVM(GB)</Name>
83 <GetScriptBlock>
84 "{0:N2}" -f ($this.AvailableVirtualMemory / 1MB)
85 </GetScriptBlock>
86 </ScriptProperty>
87 </Members>
88 </Type>
89 <Type>
90 <Name>Mr.SystemInfo</Name>
91 <Members>
92 <ScriptProperty>
93 <Name>Size(GB)</Name>
94 <GetScriptBlock>
95 "{0:N2}" -f ($this.Size / 1GB)
96 </GetScriptBlock>
97 </ScriptProperty>
98 </Members>
99 </Type>
100 <Type>
101 <Name>Mr.SystemInfo</Name>
102 <Members>
103 <ScriptProperty>
104 <Name>FreeSpace(GB)</Name>
105 <GetScriptBlock>
106 "{0:N2}" -f ($this.FreeSpace / 1GB)
107 </GetScriptBlock>
108 </ScriptProperty>
109 </Members>
110 </Type>
111 <Type>
112 <Name>Mr.SystemInfo</Name>
113 <Members>
114 <ScriptProperty>
115 <Name>PercentUsed</Name>
116 <GetScriptBlock>
117 "{0:N2}" -f (100 - ($this.FreeSpace / $this.Size * 100))
118 </GetScriptBlock>
119 </ScriptProperty>
120 </Members>
121 </Type>
122</Types>
If you're interested in learning more about types in PowerShell, I recommend taking a look at the About_Types.ps1xml help topic.
Even through I've specified the default properties to return in the types.ps1xml
file that was
previously listed, I overwrite those defaults in a format.ps1xml
file for its table view. I could
have also provided a list view in the format.ps1xml
file which would have eliminated the need to
list the default ones in the types.ps1xml file, but I wanted to show how one of these files could be
used to overwrite the other in one scenario, but not another. I've only shown the pertinent portion
of the format.ps1xml file in the following example. See
my IronScripter repository on GitHub for the entire
file and module.
1<View>
2 <Name>Mr.SystemInfo</Name>
3 <ViewSelectedBy>
4 <TypeName>Mr.SystemInfo</TypeName>
5 </ViewSelectedBy>
6 <TableControl>
7 <TableHeaders>
8 <TableColumnHeader>
9 <Width>16</Width>
10 </TableColumnHeader>
11 <TableColumnHeader>
12 <Width>50</Width>
13 </TableColumnHeader>
14 <TableColumnHeader>
15 <Width>18</Width>
16 </TableColumnHeader>
17 <TableColumnHeader>
18 <Width>6</Width>
19 </TableColumnHeader>
20 <TableColumnHeader>
21 <Width>18</Width>
22 </TableColumnHeader>
23 </TableHeaders>
24 <TableRowEntries>
25 <TableRowEntry>
26 <TableColumnItems>
27 <TableColumnItem>
28 <PropertyName>ComputerName</PropertyName>
29 </TableColumnItem>
30 <TableColumnItem>
31 <PropertyName>OSName</PropertyName>
32 </TableColumnItem>
33 <TableColumnItem>
34 <PropertyName>AvailableRAM(GB)</PropertyName>
35 </TableColumnItem>
36 <TableColumnItem>
37 <PropertyName>Drive</PropertyName>
38 </TableColumnItem>
39 <TableColumnItem>
40 <PropertyName>FreeSpace(GB)</PropertyName>
41 </TableColumnItem>
42 </TableColumnItems>
43 </TableRowEntry>
44 </TableRowEntries>
45 </TableControl>
46</View>
As with learning more about types, if you're interested in learning more about modifying the default display of objects in PowerShell, I recommend taking a look at the About_Format.ps1xml help topic.
The types.ps1xml
file has to be specified in the TypesToProcess
section of the module manifest and
the format.ps1xml
file must be specified in the FormatsToProcess
section. Once again, I'm only
showing the relevant portion of the module manifest.
1# Type files (.ps1xml) to be loaded when importing this module
2TypesToProcess = 'MrIronScripter.types.ps1xml'
3
4# Format files (.ps1xml) to be loaded when importing this module
5FormatsToProcess = 'MrIronScripter.format.ps1xml'
6
7# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
8# NestedModules = @()
9
10# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
11FunctionsToExport = 'Get-MrMonitorInfo', 'Get-MrSystemInfo'
12
13# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
14CmdletsToExport = ''
15
16# Variables to export from this module
17# VariablesToExport = @()
18
19# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
20AliasesToExport = ''
Running the function returns a nice looking table view with five properties. Notice the memory and
freespace are returned in gigabytes even though the function itself didn't convert those values to
gigabytes. That's because those properties are defined as script properties in the types.ps1xml
file.
Piping to Format-List shows additional properties and their values, but not all of the properties.
Piping to Format-List
and specifying all properties returns all of them. Certain commands will
still hide some of their properties even in this scenario and you'll either need to add the Force
parameter or pipe to Select-Object -Property *
instead to view all of them along with their
values.
Piping to Get-Member sheds some light on the different types of properties that are returned by this function.
The Get-MrSystemInfo
function and the associated files shown in this blog article can be
downloaded from
my IronScripter repository on GitHub. They can also
be installed from the PowerShell Gallery using
Install-Module which is
part of the PowerShellGet module that ships with PowerShell version 5.0 and higher. PowerShellGet
can be downloaded and installed on PowerShell version 3.0 and higher, although the module shown in
this blog article requires at least PowerShell version 4.0.
1Install-Module -Name MrIronScripter -Force
2Get-Module -Name MrIronScripter -ListAvailable
If you have a previous version installed and want to install the most recent version, Update-Module could be used instead.
1Get-Module -Name MrIronScripter -ListAvailable
2Update-Module -Name MrIronScripter -Force
3Get-Module -Name MrIronScripter -ListAvailable
Want to learn how to write PowerShell functions and script modules from a former winner of the advanced category in the scripting games and a multiyear recipient of both Microsoft’s MVP and SAPIEN Technologies’ MVP award? Then you'll definitely want to attend my Writing award winning PowerShell functions and script modules session at the PowerShell + DevOps Global Summit 2018.
µ