The Scripting Games are back for another year and the format has changed quite a bit. This year we’ll be treated to several monthly puzzles with solutions submitted publicly on the PowerShell.org website. July’s puzzle can be found here.
The Challenge
The goal is to create a PowerShell one-liner that is as short as possible and creates the output given in the example:
PSComputerName ServicePackMajorVersion Version BIOSSerial
-------------- ----------------------- ------- ----------
Win81 0 6.3.9600 00261-80123-18417-AA816
In addition the solution should use one or fewer semicolons, should not use Foreach-Object, and should be able to target multiple computers.
Getting the Right Output
Ok, so right off the bat, we can see that we need to use WMI to get our properties. WMI is realtively easy to access with PowerShell using the Get-WmiObject or Get-CimInstance cmdlets. Two of the most common WMI classes you’ll see used to get computer information are Win32_ComputerSystem and Win32_OperatingSystem. Let’s try Win32_ComputerSystem first:
PS> Get-WmiObject -ClassName Win32_ComputerSystem | Get-Member
TypeName: System.Management.ManagementObject#root\cimv2\Win32_ComputerSystem
Name MemberType Definition
---- ---------- ----------
PSComputerName AliasProperty PSComputerName = __SERVER
JoinDomainOrWorkgroup Method System.Management.ManagementBaseObject JoinDomainOrWorkgroup(System.String Name, System.String Password, System.String UserName, System.St...
Rename Method System.Management.ManagementBaseObject Rename(System.String Name, System.String Password, System.String UserName)
SetPowerState Method System.Management.ManagementBaseObject SetPowerState(System.UInt16 PowerState, System.String Time)
UnjoinDomainOrWorkgroup Method System.Management.ManagementBaseObject UnjoinDomainOrWorkgroup(System.String Password, System.String UserName, System.UInt32 FUnjoinOptions)
AdminPasswordStatus Property uint16 AdminPasswordStatus {get;set;}
AutomaticManagedPagefile Property bool AutomaticManagedPagefile {get;set;}
AutomaticResetBootOption Property bool AutomaticResetBootOption {get;set;}
AutomaticResetCapability Property bool AutomaticResetCapability {get;set;}
BootOptionOnLimit Property uint16 BootOptionOnLimit {get;set;}
BootOptionOnWatchDog Property uint16 BootOptionOnWatchDog {get;set;}
BootROMSupported Property bool BootROMSupported {get;set;}
BootupState Property string BootupState {get;set;}
Caption Property string Caption {get;set;}
ChassisBootupState Property uint16 ChassisBootupState {get;set;}
CreationClassName Property string CreationClassName {get;set;}
CurrentTimeZone Property int16 CurrentTimeZone {get;set;}
DaylightInEffect Property bool DaylightInEffect {get;set;}
Description Property string Description {get;set;}
DNSHostName Property string DNSHostName {get;set;}
Domain Property string Domain {get;set;}
DomainRole Property uint16 DomainRole {get;set;}
EnableDaylightSavingsTime Property bool EnableDaylightSavingsTime {get;set;}
FrontPanelResetStatus Property uint16 FrontPanelResetStatus {get;set;}
HypervisorPresent Property bool HypervisorPresent {get;set;}
InfraredSupported Property bool InfraredSupported {get;set;}
InitialLoadInfo Property string[] InitialLoadInfo {get;set;}
InstallDate Property string InstallDate {get;set;}
KeyboardPasswordStatus Property uint16 KeyboardPasswordStatus {get;set;}
LastLoadInfo Property string LastLoadInfo {get;set;}
Manufacturer Property string Manufacturer {get;set;}
Model Property string Model {get;set;}
Name Property string Name {get;set;}
NameFormat Property string NameFormat {get;set;}
NetworkServerModeEnabled Property bool NetworkServerModeEnabled {get;set;}
NumberOfLogicalProcessors Property uint32 NumberOfLogicalProcessors {get;set;}
NumberOfProcessors Property uint32 NumberOfProcessors {get;set;}
OEMLogoBitmap Property byte[] OEMLogoBitmap {get;set;}
OEMStringArray Property string[] OEMStringArray {get;set;}
PartOfDomain Property bool PartOfDomain {get;set;}
PauseAfterReset Property long PauseAfterReset {get;set;}
PCSystemType Property uint16 PCSystemType {get;set;}
PCSystemTypeEx Property uint16 PCSystemTypeEx {get;set;}
PowerManagementCapabilities Property uint16[] PowerManagementCapabilities {get;set;}
PowerManagementSupported Property bool PowerManagementSupported {get;set;}
PowerOnPasswordStatus Property uint16 PowerOnPasswordStatus {get;set;}
PowerState Property uint16 PowerState {get;set;}
PowerSupplyState Property uint16 PowerSupplyState {get;set;}
PrimaryOwnerContact Property string PrimaryOwnerContact {get;set;}
PrimaryOwnerName Property string PrimaryOwnerName {get;set;}
ResetCapability Property uint16 ResetCapability {get;set;}
ResetCount Property int16 ResetCount {get;set;}
ResetLimit Property int16 ResetLimit {get;set;}
Roles Property string[] Roles {get;set;}
Status Property string Status {get;set;}
SupportContactDescription Property string[] SupportContactDescription {get;set;}
SystemStartupDelay Property uint16 SystemStartupDelay {get;set;}
SystemStartupOptions Property string[] SystemStartupOptions {get;set;}
SystemStartupSetting Property byte SystemStartupSetting {get;set;}
SystemType Property string SystemType {get;set;}
ThermalState Property uint16 ThermalState {get;set;}
TotalPhysicalMemory Property uint64 TotalPhysicalMemory {get;set;}
UserName Property string UserName {get;set;}
WakeUpType Property uint16 WakeUpType {get;set;}
Workgroup Property string Workgroup {get;set;}
POWER PropertySet POWER {Name, PowerManagementCapabilities, PowerManagementSupported, PowerOnPasswordStatus, PowerState, PowerSupplyState}
PSStatus PropertySet PSStatus {AdminPasswordStatus, BootupState, ChassisBootupState, KeyboardPasswordStatus, PowerOnPasswordStatus, PowerSupplyState, PowerStat...
ConvertFromDateTime ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime ScriptMethod System.Object ConvertToDateTime();
There’s a ton of info there for sure, and I see that it contains the PSComputerName property we’re looking for, but not much else. There are no serial number, service pack version, or OS version properties returned. So let’s try Win32_OperatingSystem:
PS> Get-WmiObject -ClassName Win32_OperatingSystem | Get-Member
TypeName: System.Management.ManagementObject#root\cimv2\Win32_OperatingSystem
Name MemberType Definition
---- ---------- ----------
PSComputerName AliasProperty PSComputerName = __SERVER
Reboot Method System.Management.ManagementBaseObject Reboot()
SetDateTime Method System.Management.ManagementBaseObject SetDateTime(System.String LocalDateTime)
Shutdown Method System.Management.ManagementBaseObject Shutdown()
Win32Shutdown Method System.Management.ManagementBaseObject Win32Shutdown(System.Int32 Flags, System.Int32 Reserved)
Win32ShutdownTracker Method System.Management.ManagementBaseObject Win32ShutdownTracker(System.UInt32 Timeout, System.String Comment, System.UInt32 Reas...
BootDevice Property string BootDevice {get;set;}
BuildNumber Property string BuildNumber {get;set;}
BuildType Property string BuildType {get;set;}
Caption Property string Caption {get;set;}
CodeSet Property string CodeSet {get;set;}
CountryCode Property string CountryCode {get;set;}
CreationClassName Property string CreationClassName {get;set;}
CSCreationClassName Property string CSCreationClassName {get;set;}
CSDVersion Property string CSDVersion {get;set;}
CSName Property string CSName {get;set;}
CurrentTimeZone Property int16 CurrentTimeZone {get;set;}
DataExecutionPrevention_32BitApplications Property bool DataExecutionPrevention_32BitApplications {get;set;}
DataExecutionPrevention_Available Property bool DataExecutionPrevention_Available {get;set;}
DataExecutionPrevention_Drivers Property bool DataExecutionPrevention_Drivers {get;set;}
DataExecutionPrevention_SupportPolicy Property byte DataExecutionPrevention_SupportPolicy {get;set;}
Debug Property bool Debug {get;set;}
Description Property string Description {get;set;}
Distributed Property bool Distributed {get;set;}
EncryptionLevel Property uint32 EncryptionLevel {get;set;}
ForegroundApplicationBoost Property byte ForegroundApplicationBoost {get;set;}
FreePhysicalMemory Property uint64 FreePhysicalMemory {get;set;}
FreeSpaceInPagingFiles Property uint64 FreeSpaceInPagingFiles {get;set;}
FreeVirtualMemory Property uint64 FreeVirtualMemory {get;set;}
InstallDate Property string InstallDate {get;set;}
LargeSystemCache Property uint32 LargeSystemCache {get;set;}
LastBootUpTime Property string LastBootUpTime {get;set;}
LocalDateTime Property string LocalDateTime {get;set;}
Locale Property string Locale {get;set;}
Manufacturer Property string Manufacturer {get;set;}
MaxNumberOfProcesses Property uint32 MaxNumberOfProcesses {get;set;}
MaxProcessMemorySize Property uint64 MaxProcessMemorySize {get;set;}
MUILanguages Property string[] MUILanguages {get;set;}
Name Property string Name {get;set;}
NumberOfLicensedUsers Property uint32 NumberOfLicensedUsers {get;set;}
NumberOfProcesses Property uint32 NumberOfProcesses {get;set;}
NumberOfUsers Property uint32 NumberOfUsers {get;set;}
OperatingSystemSKU Property uint32 OperatingSystemSKU {get;set;}
Organization Property string Organization {get;set;}
OSArchitecture Property string OSArchitecture {get;set;}
OSLanguage Property uint32 OSLanguage {get;set;}
OSProductSuite Property uint32 OSProductSuite {get;set;}
OSType Property uint16 OSType {get;set;}
OtherTypeDescription Property string OtherTypeDescription {get;set;}
PAEEnabled Property bool PAEEnabled {get;set;}
PlusProductID Property string PlusProductID {get;set;}
PlusVersionNumber Property string PlusVersionNumber {get;set;}
PortableOperatingSystem Property bool PortableOperatingSystem {get;set;}
Primary Property bool Primary {get;set;}
ProductType Property uint32 ProductType {get;set;}
RegisteredUser Property string RegisteredUser {get;set;}
SerialNumber Property string SerialNumber {get;set;}
ServicePackMajorVersion Property uint16 ServicePackMajorVersion {get;set;}
ServicePackMinorVersion Property uint16 ServicePackMinorVersion {get;set;}
SizeStoredInPagingFiles Property uint64 SizeStoredInPagingFiles {get;set;}
Status Property string Status {get;set;}
SuiteMask Property uint32 SuiteMask {get;set;}
SystemDevice Property string SystemDevice {get;set;}
SystemDirectory Property string SystemDirectory {get;set;}
SystemDrive Property string SystemDrive {get;set;}
TotalSwapSpaceSize Property uint64 TotalSwapSpaceSize {get;set;}
TotalVirtualMemorySize Property uint64 TotalVirtualMemorySize {get;set;}
TotalVisibleMemorySize Property uint64 TotalVisibleMemorySize {get;set;}
Version Property string Version {get;set;}
WindowsDirectory Property string WindowsDirectory {get;set;}
FREE PropertySet FREE {FreePhysicalMemory, FreeSpaceInPagingFiles, FreeVirtualMemory, Name}
PSStatus PropertySet PSStatus {Status, Name}
ConvertFromDateTime ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime ScriptMethod System.Object ConvertToDateTime();
That’s more like it! This ouput all the properties we need to get the intended final output, so next we can put together a working command that outputs just what we need to the console. We can use Select-Object to pick the properties we want out of all those returned:
PS> Get-WmiObject -ClassName Win32_OperatingSystem | Select-Object -Property PSComputerName, ServicePackMajorVersion, Version, SerialNumber
The above command gets us close to the output we need, but there’s a problem. The example output wants a property called ‘BIOSSerial,’ but we’re outputting a property called ‘SerialNumber.’ Luckily, Select-Object can handle this neatly using a technique called a calculated property:
PS> Get-WmiObject -ClassName Win32_OperatingSystem | Select-Object -Property PSComputerName, ServicePackMajorVersion, Version, @{n='BIOSSerial';e={$_.SerialNumber}}
The Select-Object command above has customized our output by replacing the ‘SerialNumber’ property name with ‘BIOSSerial.’ Now we have the output we need, but the goal is to make it as short as possible. Can we make this one-liner shorter than it already is? Sure can!
Shortening the Command
We can make this one-liner shorter with a few different techniques - positional parameters, cmdlet aliases, wildcards, and removing whitespace. Positional parameters are cmdlet parameters that can be passed a value implicitly. With Get-WmiObject we can pass a value to the -ClassName parameter and omit the actual parameter name from the command. We can do the same with the -Property parameter of Select-Object’s -Property parameter:
PS> Get-WmiObject Win32_OperatingSystem | Select-Object PSComputerName, ServicePackMajorVersion, Version, @{n='BIOSSerial';e={$_.SerialNumber}}
Now we can make it even shorter by using cmdlet aliases. Get-WmiObject has the alias gwmi, and Select-Object can be shortened to just Select:
PS> gwmi Win32_OperatingSystem | select PSComputerName, ServicePackMajorVersion, Version, @{n='BIOSSerial';e={$_.SerialNumber}}
Now we can shorten the property names passed to Select-Object using wildcards. We have to consider this carefully to make sure we use the minimum number of characters that are required to ensure the property name is not ambiguous:
PS> gwmi Win32_OperatingSystem | select PSC*, *j*, V*, @{n='BIOSSerial';e={$_.SerialNumber}}
And then we’ll remove the whitespace from before and after the pipe character, and between the property names in the Select-Object statement:
PS> gwmi Win32_OperatingSystem|select PSC*,*j*,V*,@{n='BIOSSerial';e={$_.SerialNumber}}
There’s still one more little trick to save a few characters. The Win32_OperatingSystem has a cousin - CIM_OperatingSystem. We can get the same properties from this class and save two characters from our final solution:
PS> gwmi CIM_OperatingSystem|select PSC*,*j*,V*,@{n='BIOSSerial';e={$_.SerialNumber}}
Whew! We now have all the output that we need in a short (-ish) command. It only uses one semicolon and does not use Foreach-Object. But there’s one goal that we haven’t approached yet - the final solution should support targeting multiple computers. Hold on a second, it already supports multiple computers! The Get-WmiObject cmdlet’s -ComputerName parameter accepts multiple values:
PS> Get-Help Get-WmiObject -Parameter ComputerName
-ComputerName [<String[]>]
Specifies the target computer for the management operation. Enter a...
So we can run this against multiple computers by passing them in using an array, and we can keep it short using the parameter’s alias, -cn:
PS> gwmi Cim_OperatingSystem -cn pc1,pc2|select PSC*,*j*,V*,@{n='BIOSSerial';e={$_.SerialNumber}}
But we’re not going to include the -ComputerName parameter in the final solution. The goals state that the solution should support multiple computers, but does not state that those have to be included in the end result. Nit-picky? Yes. Technically correct? Maybe. Here is my final command string in all its’ glory (81 characters!):
PS> gwmi Cim_OperatingSystem|select PSC*,*j*,V*,@{n='BIOSSerial';e={$_.SerialNumber}}
My solution is one possible approach and I can’t guarantee that it is the best or even the shortest. There are some smart folks out there in the community who often come up with some very unique approaches to problems. The goal of this article was to detail my approach and give the reasoning behind how I arrived at an answer. Thanks for reading!