Resolving SIDs with WMI, WSMAN and PowerShell

In the world of Windows, an account SID can be a very enigmatic thing. Who is S-1-5-21-2250542124-3280448597-2353175939-1019? Fortunately, many applications, such as the event log viewer resolve the SID to an account name. The downside, is that when you are accessing that same type of information from PowerShell, you end up with the “raw’ SID. And while there are a variety of command line tools, and probably even some cool .NET trick someone will share after I post this, you most likely want to find a PowerShell solution.

Your initial assumption might be to use WMI. Searching Root\CIMv2 you’ll even find a Win32_SID class. Woohoo! This is all I need to do:

Well, no. As you can see in the figure, you can’t query this particular class.


Instead, you need to directly access the instance of the Win32_SID class. In PowerShell, the easy way is to use the [WMI] type accelerator, and specify the path to the instance.


If you wanted to query the SID on a remote computer, the path would be \\SERVERNAME\root\cimv2:CLASSNAME.Keyproperty=’Something’. But be aware that there is no way to specify alternate credentials using [WMI]. Although, you could query the Win32_Account class for the SID.

This gives you the benefit of using a cmdlet, querying a remote computer and using alternate credentials.

In PowerShell 3.0 if you want to use the new CIM cmdlets and query WMI over WSMan, it is pretty easy to turn the previous command into a CIM command.

These queries are pretty good, but I believe that if you can go straight to the instance, so much the better. Unfortunately, I can’t find any CIM related accelerator that would give me the same result as using the [WMI] accelerator. Remember, my goal is to leverage the new WSMan protocol. The solution is to use the Get-WSManInstance cmdlet.

I think you can tell that the ResourceUri is the path to the class and the SelectorSet is a hashtable with key property, in this case SID, and the corresponding value. The result looks a little different, but the critical parts, like the account name are there.

Get-WSManInstance also supports alternate credentials. So given all of this, I put together a function called Resolve-SID that uses this approach. But as a fallback, you can also tell it to use WMI.

I think between the comment based help, internal comments and verbose messages you should be able to understand how the function works. So now you have a variety of techniques for resolving SIDs. Querying locally, using [WMI] or querying the Win32_Account class for the SID should be sufficient performance-wise. But remotely, using [WMI] or Get-WSManInstance is significantly faster than querying and filtering. Using Get-WMIboject or Get-CIMInstance took between 600-750ms, where as the [WMI]approach took about 200MS and using Get-WSManInstance took 150MS.

I hope you are resolved to not let SIDS stand in your way any longer.

PowerShell Morning Report with Credentials

I had an email about trying to use my Morning Report script to connect to machines that required alternate credentials. For example, you might have non-domain systems in a DMZ. Fair enough. Since most of the report script uses WMI, it wasn’t too hard to add a Credential parameter and modify the WMI code to use it. I tweaked the code a bit to use hashtables to splat parameters.

#region define a parameter hashtable
[email protected]{

if ($credential) {
If ($OK) {

Try {
#get Operating system information from WMI
$os = Get-WmiObject @paramhash

I’m a little mixed on using splatting in the script. On one hand it makes it easier to wrap up parameters but the actual command might be a little confusing. Hopefully the comments make it clear.

So that handled all the WMI parts. The event log section is using Get-Eventlog which doesn’t have a -Credential parameter. I could have tried to rewrite the section using WMI, but that seemed like a lot of work. So I made the assumption that the computers you are querying are running PowerShell 2 or later with remoting enabled. That means I can use Invoke-Command to run Get-Eventlog ON the remote computer. As an additional benefit this seems to run a little faster, at least in my testing.

The tricky part was passing all the parameter values to Get-EventLog and Invoke-Command. I ended up with some complicated nesting but it works.

#Event log errors and warnings in the last $Hours hours
#define a hash table of parameters to splat to Get-Eventlog
$GetEventLogParam = @{

#System Log
Write-Host "...System Event Log Error/Warning since $last" -ForegroundColor Cyan
#hashtable of optional parameters for Invoke-Command
$InvokeCommandParam = @{
ScriptBlock = {Param ($params) Get-EventLog @params }

if ($Credential) { $InvokeCommandParam.Add("Credential",$Credential) }

$syslog = Invoke-Command @InvokeCommandParam

$syslogdata = $syslog | Select TimeGenerated,EventID,Source,Message

#Application Log
Write-Host "...Application Event Log Error/Warning since $last" -ForegroundColor Cyan
#update the hashtable

#update invoke-command parameters
$InvokeCommandParam.ArgumentList = $GetEventLogParam

$applog = Invoke-Command @InvokeCommandParam
$applogdata = $applog | Select TimeGenerated,EventID,Source,Message

Now you can run the script using -Credential, specifying either a saved credential object or the user name which will give you the Get-Credential prompt. I also made some slight tweaks to the embedded style and layout.

morning report

If you missed the original and related posts, you might want to read:

Download the latest version of the MorningReport.

Find Files with WMI and PowerShell

magnifying-glass-text-label-searchFinding files is one of those necessary evils for IT Pros. Sometimes we’re searching for a needle in a haystack. And it gets even more complicated when the haystacks are on 10 or 100 or 1000 remote computers. You might think using Get-ChildItem is your only option. Certainly it works, but if you are searching an entire hard drive it is pretty resource intensive.

Another option is to use WMI and CIM_Datafile class. Don’t let the name fool you. You can use Get-WmiObject in PowerShell 2.0 or 3.0. Every file, as far as I know, is also registered with WMI so all you need to do is query for all instances of the CIM_Datafile class. However, you must be clever. Just like searching an entire drive, searching via WMI can be time consuming. So you need to make your WMI query as specific as possible. To do that you need to know the properties. Here’s what a CIM_Datafile object looks like.

Status : OK
Name : c:\program files (x86)\windows defender\mpclient.dll
__GENUS : 2
__CLASS : CIM_DataFile
__SUPERCLASS : CIM_LogicalFile
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : CIM_DataFile.Name="c:\\program files (x86)\\windows defender\\mpclient.dll"
__DERIVATION : {CIM_LogicalFile, CIM_LogicalElement, CIM_ManagedSystemElement}
__NAMESPACE : root\cimv2
__PATH : \\SERENITY\root\cimv2:CIM_DataFile.Name="c:\\program files (x86)\\windows d
AccessMask : 17957033
Archive : True
Caption : c:\program files (x86)\windows defender\mpclient.dll
Compressed : False
CompressionMethod :
CreationClassName : CIM_LogicalFile
CreationDate : 20120725214205.814611-240
CSCreationClassName : Win32_ComputerSystem
Description : c:\program files (x86)\windows defender\mpclient.dll
Drive : c:
EightDotThreeFileName : c:\program files (x86)\windows defender\mpclient.dll
Encrypted : False
EncryptionMethod :
Extension : dll
FileName : mpclient
FileSize : 662016
FileType : Application Extension
FSCreationClassName : Win32_FileSystem
Hidden : False
InstallDate : 20120725214205.814611-240
InUseCount :
LastAccessed : 20120725214205.814611-240
LastModified : 20120725231905.556000-240
Manufacturer : Microsoft Corporation
Path : \program files (x86)\windows defender\
Readable : True
System : False
Version : 4.0.9200.16384
Writeable : True
Scope : System.Management.ManagementScope
Options : System.Management.ObjectGetOptions
ClassPath : \\SERENITY\root\cimv2:CIM_DataFile
Properties : {AccessMask, Archive, Caption, Compressed...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers : {dynamic, Locale, provider, UUID}
Site :
Container :

At a minimum you should limit your query to the drive. Otherwise the WMI query will search ALL drives. If you are searching by path, description or caption, don’t forget that the \ character needs to be escaped, e.g. \\program files (x86)\\windows defender\\. Of course if you know that much already you might as well use Get-Childitem.

For me, the real benefit in using WMI is when I know the file name but don’t know for sure where it might be on a given drive. So I put together an advanced function called Get-CIMFile.

Function Get-CIMFile {
#comment based help is here

[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]

<# strip off any trailing characters to drive parameter that might have been passed. #>
If ($Drive.Length -gt 2) {

Write-Verbose "Searching for $Name on Drive $Drive on computer $Computername."

<# Normally you might think to simply split the name on the . character. But you might have a filename like myfile.v2.dll so that won't work. In a case like this the extension would be everything after the last . and the filename everything before. So instead I'll use the substring method to "split" the filename string. #>

#get the index of the last .
$index = $name.LastIndexOf(".")
#get the first part of the name
#get the last part of the name

$filter = "Filename='$filename' AND extension='$extension' AND Drive='$drive'"
Write-Verbose $filter

#get all instances of the file and write the WMI object to the pipeline
Get-WmiObject -Class CIM_Datafile -Filter $filter -ComputerName $Computername -Asjob:$AsJob

} #end Get-CIMFile

The code is documented to explain what is going on so I won’t repeat it here. The function will work anywhere you have WMI access. This version doesn’t handle alternate credentials or other features of Get-WmiObject, which you could add if you want. But with this I can check files on multiple computers. Suppose I’m concerned about a vulnerability like the recent Java problem. Or I need to see if any computers are out of date on a given file. I can run a command like this.

PS Scripts:\> $files = get-cimfile mpclient.dll -comp serenity,novo8
PS Scripts:\> $files | Sort name,CSName | Select Name,Version,CSName

Name Version CSName
---- ------- ------
c:\program files (x86)\windows... 4.0.9200.16384 SERENITY
c:\program files\windows defen... 4.0.9200.16384 NOVO8
c:\program files\windows defen... 4.0.9200.16384 SERENITY
c:\windows\winsxs\amd64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\wow64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\x86_windows-... 4.0.9200.16384 NOVO8

The command writes the full WMI object to the pipeline so I could filter or format $files however I need.

Download Get-CIMFile and let me know what you think.

Skipping WMI System Properties in PowerShell

One of my favorite techniques when using WMI in PowerShell is to pipe an object to Select-Object and select all properties. Try this:

get-wmiobject win32_bios | select *

It works, but it also gets all of the system properties like __PATH which I rarely care about. I also get other properties like Site and Options which I typically don’t need. So here are some techniques you can use to view all WMI properties that probably matter most. Let’s begin with a typical command:

$os=get-wmiobject Win32_OperatingSystem

This object has a property called Properties which is a collection of WMI PropertyData objects. I could try something like:

PS C:\> $ | select name,value

Name Value
---- -----
BootDevice \Device\HarddiskVolume1
BuildNumber 7601
BuildType Multiprocessor Free
Caption Microsoft Windows 7 Ultimate
CodeSet 1252
CountryCode 1

Sure, I can see the properties and values but I don’t really have a good object to work with and this won’t help with multiple instances, at least not without a little extra work.

$d=Get-WmiObject win32_logicaldisk -filter "drivetype=3"
$d | foreach {$ | select name,value }

And maybe that’s all you need. If so, terrific. But I’m looking for an elegant solution. How about expanding the property names and saving them as an array of strings.

[string[]]$prop=$ | Select -expand name
$os | Select -Property $prop

Then I can use them with Select-Object

PS C:\> $os | Select -Property $prop

PS C:\> $os | Select -Property $prop

BootDevice : \Device\HarddiskVolume1
BuildNumber : 7601
BuildType : Multiprocessor Free
Caption : Microsoft Windows 7 Ultimate
CodeSet : 1252
CountryCode : 1

That has promise. I could even turn this into a one-line command, assuming I’ve already defined $os.

$os | Select -property ([string[]]($ | Select -expand name))

If I didn’t want to take the extra step, here’s a complex alternative:

Get-WmiObject win32_OperatingSystem | foreach {
Select -InputObject $_ -Property ([string[]](Select -InputObject $_ -expand Properties | Select -expand Name))

But this leads to yet another option, using the [WMIClass] type accelerator. When used, PowerShell creates an empty object of the specified WMI class which includes a property list.

PS C:\> [wmiclass]"win32_operatingsystem" | Select -expand Properties | Select name


This can be further simplified:

([wmiclass]"win32_operatingsystem").Properties | Select -expand Name

In fact, I can use this in an earlier expression to get the same non-System class results.

get-wmiobject win32_operatingsystem | select -property ([string[]](([wmiclass]"Win32_Operatingsystem").properties | select -expand name))

I’ll admit that’s a lot to type. So I need a shortcut. What about turning this into a scriptblock?

$p={[string[]](([wmiclass]"Win32_Operatingsystem").properties | select -expand name)}

When I invoke the scriptblock, I’ll get the array of property names. I can now use this in my Get-WMIObject expression:

get-wmiobject win32_operatingsystem | select -property (&$p)

Definitely easier to type, but limited. I need the scriptblock to be more flexible so it can accommodate other WMI classes.

$p={Param([string]$class) [string[]](([wmiclass]$class).properties | select -expand name) }

I’ll test it in the shell.

PS C:\> &$p "win32_bios"

Excellent! Now for the real deal:

PS C:\> get-wmiobject win32_bios | select -property (&$p "win32_bios")

BiosCharacteristics : {4, 7, 8, 9...}
BIOSVersion : {TOSQCI - 6040000, Ver 1.00PARTTBL}
BuildNumber :
Caption : Ver 1.00PARTTBL
CodeSet :
CurrentLanguage :
Description : Ver 1.00PARTTBL

That’s exactly what I wanted with minimal effort. All I need is to have that scriptblock loaded in my PowerShell session and remember to specify the class name.

get-wmiobject win32_logicaldisk -filter "drivetype=3" | select -property (&$p "win32_logicaldisk")

Another option would be to turn the scriptblock into a function, but I’ll leave that to you.

Naturally this isn’t perfect. If I need to query a remote computer with classes that aren’t on my computer, this won’t work; at least not without some revisions. But since I’d say 90% or more of WMI commands in PowerShell are with the Win32 classes, I think this is a handy trick to stick in your PowerShell toolbox.

If you’d like to try some of my code samples, you can download demo-wmiproperties.

Morning Report Revised

Last month I posted a PowerShell script I called The Morning Report. I received some very nice feedback. One comment was about making it easier to use the script in a pipelined expression. For example, get a list of computers from a text file and create a single HTML report. That sounds reasonable to me so I decided to revisit the script and add a few tweaks.

The latest version can still be used as I originally described. But now the computername parameter can be piped in to the script.

PS C:\> $report=get-content computers.txt | c:\scripts\morningreport.ps1

The report will still write a collection of custom report objects to the pipeline. But now I can do this:

PS C:\> get-content computers.txt | c:\scripts\morningreport.ps1 -text | out-file c:\work\morning-report.txt

This will create a single text file for all computers in the list. If you prefer a separate file per computer, use a ForEach loop.

PS C:\> get-content computers.txt | foreach {
>> $comp=$_
>> c:\scripts\morningreport.ps1 -Comp $comp -text |
>> out-file "c:\work\$comp-morning-report.txt"
>> }

Similarly, I can now create a single HTML file.

PS C:\> get-content computers.txt | c:\scripts\morningreport.ps1 -html -hours 36 | out-file c:\work\morning-report.htm

I had to revise the HTML related code a bit to accommodate multiple computers in the same file. I modified the navigation links to the computername and inserted a list of computers at the beginning of the file. Clicking on a computername will take you to the corresponding summary. I probably wouldn’t try this with hundreds of computers, but for most small to mid-size groups I think this will work just fine.

The last change I made, which was as much for me as anyone, was to add a -Quick parameter. Querying event logs is time consuming so if you are in a hurry you can skip the event log query by using -Quick. You’ll still get an event log section in the output, but it will be empty.

Download the revised version of MorningReport.ps1 and let me know what you think.