Tag Archives: WMI

Test 64-Bit Operating System

One of the great features of PowerShell is how much you can get from a relatively simple one line command. For example. you might want to test if a computer is running a 64-bit operating system. You can find out with a command as simple as this.

If you are running PowerShell 3 you could substitute Get-CimInstance. One thing to be aware of with this particular class is that the OSArchitecture property isn’t valid on older operating systems like Windows Server 2003. You’ll get an exception.

osarchitecture-fail

In this case I modified the WMI query to only return the OSArchitecture property, which doesn’t exist. Otherwise I would just get false which might not be entirely true. Of course, I always get carried away so before I knew it I had turned this one line command into a function.

The function takes a computername and optionally a PSCredential. You can use a saved PSCredential object or specify the name and you will be prompted.

Test-Is64Bit-01
The other design element is that if there is an exception caught I have a Switch statement to checks the message and writes a custom warning.
Test-Is64Bit-02

There’s no reason you can’t use the one-liner. But if you want to add a bit more robustness and create a re-usable tool, it doesn’t take much to turn it into a simple function.

Get CIMInstance from PowerShell 2.0

I love the new CIM cmdlets in PowerShell 3.0. Querying WMI is a little faster because the CIM cmdlets query WMI using the WSMAN protocol instead of DCOM. The catch is that remote computers must be running PowerShell 3 which includes the latest version of the WSMAN protocol and the WinRM service. But if your computers are running 3.0 then you can simply run a command like this:

However, if one of the computers is running PowerShell 2.0, you’ll get an error.

get-ciminstance-error

In this example, CHI-DC02 is not running PowerShell 3.0. The solution is to create a CIMSession using a CIMSessionOption for DCOM.

get-ciminstance-dcom

So there is a workaround, but you have to know ahead of time which computers are not running PowerShell 3.0. When you use Get-CimInstance and specify a computername, the cmdlet setups up a temporary CIMSession. So why not create the temporary CIMSession with the DCOM option if it is needed? So I wrote a “wrapper” function called Get-MyCimInstance to do just that.

The heart of the function is a nested function to test if a remote computer is running WSMAN 3.0.

The function uses Test-WSMan and a regular expression to get the remoting version. If it is 3.0 the function returns True. In Get-MyCIMInstance I test each computer and if not running 3.0, create the CIMSession option and include it when creating the temporary CIMSession.

I’m using a Try/Catch block because if the computer is offline, my test function will throw an exception which I can catch.

Otherwise, all is good and ย I can pass the rest of the parameters to Get-CimInstance.

At the end of the process, I remove the temporary CIMSession. With this, now I can query both v2 and v3 computers.
get-myciminstance01
Notice for CHI-DC02 I’m creating the DCOM option. Here’s the command without all the verboseness.
get-myciminstance02
I could have created a proxy function for Get-CimInstance, but not only are they more complicated, I didn’t want that much transparency. I wanted to know that I’m querying using my function and not Get-CimInstance. Here’s the complete script.

I hope you’ll let me know what you think and if you find this useful.

UPDATE: I’ve revised this script and article since it’s original posting to better handle errors if you can’t test WSMAN. I also added support for alternate credentials, which is something you can’t do with Get-CimInstance.

WMI Explorer from The PowerShell Guy

Several years ago, The PowerShell Guy, aka MoW, wrote a fantastic graphical PowerShell script that was a WMI Explorer. With this script you could connect to a computer and namespace, browse classes and view instances. A great way for discovering things about WMI.

wmibrowser

However Marc has moved on to other things I think and his original site, at least right now, no longer seems to be running. So in the spirit of community, and I hope he won’t mind, I’m sharing my version. Download PowerShellGuy-WmiExplorer.zip and extract the script file. Run the script, and enjoy.

Update 3/12/2013
I didn’t realize the script is digitally signed. However, most likely some of the certificates in the trust chain originally used 6-7 years ago have probably expired. You might get an error about unable to validate a certificate chain. If that is the case, open the script in the ISE or Notepad and go to the end. Delete the commented signature block, save the file and try again. If your execution policy is AllSigned, you will need to resign the script.

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
$paramhash=@{
Classname="Win32_OperatingSystem"
Computername=$Computername
ErrorAction="Stop"
}

if ($credential) {
$paramhash.Add("Credential",$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
$last=(Get-Date).AddHours(-$Hours)
#define a hash table of parameters to splat to Get-Eventlog
$GetEventLogParam = @{
LogName="System"
EntryType="Error","Warning"
After=$last}

#System Log
Write-Host "...System Event Log Error/Warning since $last" -ForegroundColor Cyan
#hashtable of optional parameters for Invoke-Command
$InvokeCommandParam = @{
Computername=$Computername
ArgumentList=$GetEventLogParam
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
$GetEventLogParam.LogName="Application"

#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:
http://jdhitsolutions.com/blog/2012/01/the-powershell-morning-report/
http://jdhitsolutions.com/blog/2012/02/morning-report-revised/
http://jdhitsolutions.com/blog/2012/08/event-log-morning-report/

Download the latest version of the MorningReport.

Find Files with WMI and PowerShell Revisited

computereyeLast week I posted a PowerShell function to find files using WMI. One of the comments I got was about finding files with wildcards. In WMI, we’ve been able to search via wildcards and the LIKE operator since the days of XP.

In a WMI query we use the % as the wildcard character. Here’s an example:


PS C:\scripts> get-wmiobject win32_service -filter "displayname LIKE 'Micro%'" | Select Name,Displayname,State,Startmode

Name Displayname State Startmode
---- ----------- ----- ---------
MSiSCSI Microsoft iSCSI Initi... Stopped Manual
swprv Microsoft Software Sh... Stopped Manual
wlidsvc Microsoft Account Sig... Stopped Manual

So it wasn’t especially difficult to revise my original function to accept wildcards as part of the file name. Since we are used to using * as the wildcard character, I assumed it would be used here as well. So all I had to do was replace the * with % and change the operator.


#if there is * in the filename or extension, replace it with %
#and change the comparison operator for the WMI query
if ($filename -match "\*" ) {
Write-Verbose "Wildcard search on filename"
$filename = $filename.Replace("*","%")
$fileOp="LIKE"
}
else {
$fileOp="="
}
if ($extension -match "\*") {
Write-Verbose "Wildcard search on extension"
$extension = $extension.Replace("*","%")
$extOp="LIKE"
}
else {
$extOp="="
}
$filter = "Filename $fileOp '$filename' AND extension $extOp '$extension' AND Drive='$drive'"
Write-Verbose $filter

The reason I didn’t simply make the expression use LIKE all the time is performance. When you use the LIKE operator, there is a significant performance price to pay. So I only wanted to use LIKE if I really had to.

The other change I made was to accept an alternate credential (using a technique Boe Prox turned me on to).


...
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
...

If you pass a string like mydomain\admin, you’ll get prompted for the password. Or you can pass a previously created credential object. Here’s the revised code, less the comment based help.


#requires -version 2.0

Function Get-CIMFile {

[cmdletbinding(DefaultParameterSetName="Default")]
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidateNotNullorEmpty()]
[string]$Drive="C:",
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
[Parameter(ParameterSetName="Job")]
[switch]$AsJob,
[Parameter(ParameterSetName="Job")]
[int32]$ThrottleLimit=32
)

<#
strip off any trailing characters to drive parameter
that might have been passed.
#>
If ($Drive.Length -gt 2) {
$Drive=$Drive.Substring(0,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
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)

#if there is * in the filename or extension, replace it with %
#and change the comparison operator for the WMI query
if ($filename -match "\*" ) {
Write-Verbose "Wildcard search on filename"
$filename = $filename.Replace("*","%")
$fileOp="LIKE"
}
else {
$fileOp="="
}
if ($extension -match "\*") {
Write-Verbose "Wildcard search on extension"
$extension = $extension.Replace("*","%")
$extOp="LIKE"
}
else {
$extOp="="
}
$filter = "Filename $fileOp '$filename' AND extension $extOp '$extension' AND Drive='$drive'"
Write-Verbose $filter

#build the core command
$cmd="Get-WmiObject -Class CIM_Datafile -Filter ""$filter"" -ComputerName $Computername"

if ($credential) {
write-Verbose "Adding credential for $($credential.username)"
$cmd+=" -credential `$credential"

} #if credential

#get all instances of the file and write the WMI object to the pipeline
if ($AsJob) {
Write-Verbose "Running query as a job"
$cmd+=" -Asjob -ThrottleLimit $ThrottleLimit"
}
else {
#record the start time
$start=Get-Date
}

Write-Verbose $cmd
Invoke-Expression -Command $cmd

#display how long this took if not running as a job
if ($start) {
#get the end time and report how long the search took
$end=Get-Date
Write-Verbose "Search completed in $($end-$start)"
}

} #end Get-CIMFile

The last major change is that I construct a Get-WmiObject command string based on the parameter values. I start with a core command.


#build the core command
$cmd="Get-WmiObject -Class CIM_Datafile -Filter ""$filter"" -ComputerName $Computername"

And then add other parameters based on the user’s input. I use Invoke-Expression to run the final command.

You can download my revised version of Get-CIMFile

Finally, while I was working on this revision PowerShell madman Boe Prox was working up his own version which has even more bells and whistles. Many of the articles I write here are intended as tutorials and not necessarily production ready tools. Boe’s version on the other hand is on PEDs (PowerShell Enhancing Drug). Check it out.

Building Excel Reports with PowerShell

Last year I wrote a series of articles for the Petri IT KnowledgeBase on using Microsoft Excel with PowerShell. Today I received an email from a reader who had a question about article that showed how to build a drive usage report in Excel. In the article I suggest it wouldn’t be too difficult to modify my sample code to create a worksheet for each computer. Perhaps I was a bit too glib. So I decided to revise my sample script so that you could pass an array of computernames and build a single workbook with a named worksheet for each computer.


#Requires -version 2.0

<#
this version takes an array of computer names and creates a new
worksheet for each one.
#>

[cmdletbinding()]
Param([string[]]$computername=$env:computername)

#I hope it goes without saying that Excel needs to be installed
Write-Verbose "Creating Excel application"
$xl=New-Object -ComObject "Excel.Application"
$wb=$xl.Workbooks.Add()

#we'll need some constants
$xlConditionValues=[Microsoft.Office.Interop.Excel.XLConditionValueTypes]
$xlTheme=[Microsoft.Office.Interop.Excel.XLThemeColor]
$xlChart=[Microsoft.Office.Interop.Excel.XLChartType]
$xlIconSet=[Microsoft.Office.Interop.Excel.XLIconSet]
$xlDirection=[Microsoft.Office.Interop.Excel.XLDirection]

Foreach ($computer in $computername) {
#get disk data
Write-Verbose "Getting disk data from $computer"
$disks=Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer -Filter "DriveType=3"

Write-Verbose "Adding Worksheet"
$ws=$wb.Worksheets.Add()

$cells=$ws.Cells

$cells.item(1,1)="Disk Drive Report"

#define some variables to control navigation
$row=3
$col=1

#insert column headings
Write-Verbose "Adding drive headings"

"Drive","SizeGB","FreespaceGB","UsedGB","%Free","%Used" | foreach {
$cells.item($row,$col)=$_
$cells.item($row,$col).font.bold=$True
$col++
}

Write-Verbose "Adding drive data"
foreach ($drive in $disks) {
$row++
$col=1
$cells.item($Row,$col)=$drive.DeviceID
$col++
$cells.item($Row,$col)=$drive.Size/1GB
$cells.item($Row,$col).NumberFormat="0"
$col++
$cells.item($Row,$col)=$drive.Freespace/1GB
$cells.item($Row,$col).NumberFormat="0.00"
$col++
$cells.item($Row,$col)=($drive.Size - $drive.Freespace)/1GB
$cells.item($Row,$col).NumberFormat="0.00"
$col++
$cells.item($Row,$col)=($drive.Freespace/$drive.size)
$cells.item($Row,$col).NumberFormat="0.00%"
$col++
$cells.item($Row,$col)=($drive.Size - $drive.Freespace) / $drive.size
$cells.item($Row,$col).NumberFormat="0.00%"
}

Write-Verbose "Adding some style"

#add some style
$range=$ws.range("A1")
$range.Style="Title"
#or set it like this
$ws.Range("A3:F3").Style = "Heading 2"

#adjust some column widths
Write-Verbose "Adjusting column widths"
$ws.columns.item("C:C").columnwidth=15
$ws.columns.item("D:F").columnwidth=10.5
$ws.columns.item("B:B").EntireColumn.AutoFit() | out-null

#add some conditional formatting
Write-Verbose "Adding conditional formatting"

#get the starting cell
$start=$ws.range("F4")
#get the last cell
$Selection=$ws.Range($start,$start.End($xlDirection::xlDown))
#add the icon set
$Selection.FormatConditions.AddIconSetCondition() | Out-Null
$Selection.FormatConditions.item($($Selection.FormatConditions.Count)).SetFirstPriority()
$Selection.FormatConditions.item(1).ReverseOrder = $True
$Selection.FormatConditions.item(1).ShowIconOnly = $False
$Selection.FormatConditions.item(1).IconSet = $xlIconSet::xl3TrafficLights1
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Type=$xlConditionValues::xlConditionValueNumber
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Value=0.8
$Selection.FormatConditions.item(1).IconCriteria.Item(2).Operator=7
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Type=$xlConditionValues::xlConditionValueNumber
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Value=0.9
$Selection.FormatConditions.item(1).IconCriteria.Item(3).Operator=7

#insert a graph
Write-Verbose "Creating a graph"
$chart=$ws.Shapes.AddChart().Chart
$chart.chartType=$xlChart::xlBarClustered

$start=$ws.range("A3")
#get the last cell
$Y=$ws.Range($start,$start.End($xlDirection::xlDown))
$start=$ws.range("F3")
#get the last cell
$X=$ws.Range($start,$start.End($xlDirection::xlDown))

$chartdata=$ws.Range("A$($Y.item(1).Row):A$($Y.item($Y.count).Row),F$($X.item(1).Row):F$($X.item($X.count).Row)")
$chart.SetSourceData($chartdata)

#add labels
$chart.seriesCollection(1).Select() | Out-Null
$chart.SeriesCollection(1).ApplyDataLabels() | out-Null
#modify the chart title
$chart.ChartTitle.Text = "Utilization"
Write-Verbose "Repositioning graph"
$ws.shapes.item("Chart 1").top=40
$ws.shapes.item("Chart 1").left=400

Write-Verbose "Renaming the worksheet"
#rename the worksheet
$name=$disks[0].SystemName
$ws.Name=$name
#select A1
$ws.Range("A1").Select() | Out-Null
} #foreach

#delete extra sheets
Write-Verbose "Deleting extra worksheets"
$xl.worksheets.Item("Sheet1").Delete()
$xl.Worksheets.Item("Sheet2").Delete()
$xl.Worksheets.Item("Sheet3").Delete()

#make Excel visible
$xl.Visible=$True

$filepath=Read-Host "Enter a path and filename to save the file"

if ($filepath) {
Write-Verbose "Saving file to $filepath"
$wb.SaveAs($filepath)
$xl.displayAlerts=$False
$wb.Close()
$xl.Quit()
}

#end of script

If you looked at the original post, here are the changes I made. First, I modified the script parameters to take an array of computer names.


Param([string[]]$computername=$env:computername)

I decided to change the parameter name as well which worked out well because I could keep most of my code for each computer and simply insert a ForEach loop.


Foreach ($computer in $computername) {
#get disk data
Write-Verbose "Getting disk data from $computer"
$disks=Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer -Filter "DriveType=3"
...

This also meant I needed to move some code to before this loop since I only have to create the Excel application and workbook objects once. For each computer I use WMI to get disk data and create a new worksheet.


Write-Verbose "Adding Worksheet"
$ws=$wb.Worksheets.Add()

The rest of the Excel-related code remains the same for each computer. But I did alter the code to rename each worksheet.


#rename the worksheet
$name=$disks[0].SystemName
$ws.Name=$name
...

I also decided to delete the “extra” sheets. Unless you’ve modified Excel’s startup behavior new workbooks have Sheet1, Sheet2 and Sheet3 which I don’t need so I delete them.


#delete extra sheets
Write-Verbose "Deleting extra worksheets"
$xl.worksheets.Item("Sheet1").Delete()
$xl.Worksheets.Item("Sheet2").Delete()
$xl.Worksheets.Item("Sheet3").Delete()

With this new script I can run a command like this:


PS Scripts:\> .\New-ExcelDiskSpace2A.ps1 -computer novo8,jdhit-dc01 -Verbose

Which gives me a single workbook with a worksheet for each computer:

MultiExcelDiskReport

Download my revised script New-ExcelDiskSpace2A.

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"
__PROPERTY_COUNT : 33
__DERIVATION : {CIM_LogicalFile, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : SERENITY
__NAMESPACE : root\cimv2
__PATH : \\SERENITY\root\cimv2:CIM_DataFile.Name="c:\\program files (x86)\\windows d
efender\\mpclient.dll"
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
CSName : SERENITY
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
FSName : NTFS
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

[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidateNotNullorEmpty()]
[string]$Drive="C:",
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[switch]$AsJob
)

<#
strip off any trailing characters to drive parameter
that might have been passed.
#>
If ($Drive.Length -gt 2) {
$Drive=$Drive.Substring(0,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
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)

$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.