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

TechDays SF Presentations

TechDays_logo250 Last week I presented a number of sessions at TechDays in beautiful San Francisco. Met some great people and had a great time. I presented 4 talks, almost all of them PowerShell-related. Actually, they all had some type of PowerShell content. I’m happy to share my session slides and PowerShell demonstrations. Most of the demonstrations are not full-blown scripts but command examples, except for those things labeled as functions. If you did not attend TechDays, you are still welcome to download the material, although without the context of the live presentation some of it may not make sense. I hope you can make it next time.

File and Folders with Powershell 3
If you manage file servers and aren’t using PowerShell, you are working much too hard. Or if you are using PowerShell v2 you are still working pretty hard. Fortunately PowerShell v3 along with Windows 8 and Windows Server 2012 offer a much better solution. This session will demonstrate how to provision and manage folders, files and file shares using PowerShell from a Windows 8 client. With a little up-front work, you ‘ll be able to create provisioning scripts to deploy a new file share in seconds.

10 PowerShell mistakes, trip-ups and traps
Windows PowerShell is a language and management technology that many IT professionals, including developers, think they understand. Yet very often they get caught up in pre-conceptions and misinterpretations, usually based on prior experience with scripting or development. This session will explore the 10 most common mistakes and traps that people fall into with PowerShell and how to avoid them.

Troubleshooting Active Directory with PowerShell
Active Directory is one of those technologies that when it works, nobody notices. But when it doesn’t work, everyone does. Fortunately, Windows PowerShell and Windows Server 2012 make a terrific troubleshooting tool. In this session we’ll look at some common Active Directory problems, how to diagnose them and in some cases resolve, all with Windows PowerShell.

Building a Windows 8 Hyper-V lab
We all know the benefits of testing in a non-production environment. But sometimes resources are limited and having a test setup seems like a lot of work. But now that Windows 8 includes Hyper-V, you can setup a lab environment in very little time. This session will guide you through setting up a Hyper-V based test lab and how to get the most out of it using the PowerShell management tools.

If you didn’t catch me in San Francisco, I’ll be at TechMentor this fall in Las Vegas. More on that later. There’s a chance I’ll be back to the West coast later this year for more PowerShell goodness. Keep an eye on the blog for announcments. Or if your company is looking for training, let’s talk.

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 PowerShell 3.0

My last few articles have looked at using WMI and CIM_DATAFILE class to find files, primarily using Get-WmiObject in PowerShell. But now that we have PowerShell 3.0 at our disposal, we can use the new CIM cmdlets. So I took my most recent version of Get-CIMFile and revised it specifically to use Get-CimInstance. I also took the liberty of adding a few more refinements, some of which you could integrate into previous versions. Here’s the new v3 function.


Function Get-CIMFile {
#comment basedhelp

[cmdletbinding(DefaultParameterSetName="Computername")]

Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidatePattern("^[a-zA-Z]:$")]
[string]$Drive="C:",
[Parameter(ParameterSetName="Computername")]
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[Parameter(ParameterSetName="CIMSession")]
[ValidateNotNullorEmpty()]
[Microsoft.Management.Infrastructure.CimSession[]]$CimSession
)

Write-Verbose "Starting $($MyInvocation.MyCommand)"
Write-Verbose "Parameter set = $($PSCmdlet.ParameterSetName)"

#create a hashtable of parameter values that can be splatted to Get-CimInstance
$paramHash=@{Classname="CIM_DATAFILE"}

Write-Verbose "Searching for $filename on drive $drive"
if ($pscmdlet.ParameterSetName -eq "Computername") {
Write-Verbose "...on $computername"
$paramHash.Add("Computername",$computername)
}
elseif ($pscmdlet.ParameterSetName -eq "CimSession") {
Write-Verbose "...on $Cimsession"
$paramHash.Add("CimSession",$cimSession)
}
else {
#this should never happen
Write-Verbose "No computername or cimsession specified. Defaulting to local host"
#bail out of the function
Return
}

#define default operators
$fileOp="="
$extOp="="

<#
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(".")

#it is possible the filename doesn't have an extension
if ($index -gt 0) {
#get the first part of the name
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)
}
else {
$filename=$Name
#will need to use wildcard search for filename when extension is empty
$fileop="LIKE"
$extension=$null
}
#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"
}

if ($extension -match "\*") {
Write-Verbose "Wildcard search on extension"
$extension = $extension.Replace("*","%")
$extOp="LIKE"
}

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

#add the filter to the hashtable
$paramHash.Add("Filter",$filter)

#invoke the command
Write-Verbose "Parameter Hash $($paramHash| out-String)"

#let's time how long it took
$start=Get-Date

Get-CimInstance @paramhash

$end=Get-Date
Write-Verbose "Search completed in $($end-$start)"
Write-Verbose "Ending $($MyInvocation.MyCommand)"

} #end Get-CIMFile

This version let’s you search remote computers by name or CIM session. Because they are mutually exclusive options, this function uses parameter sets, defaulting to using a computername. I also added a validation check on the drive name using regular expressions. The function will fail if the value is not a letter followed by a colon.

Another major change was modifying code to search for filenames without an extension. What if you are looking for a file like README? The WQL query turned out to be more complicated than I imagined. It would be easier if the extension property was NULL, but it isn’t. It is a 0 length string. I found that in order to make this work, I needed to create a query like this:

SELECT * FROM CIM_DATAFILE WHERE Filename LIKE ‘readme’ AND extension = ” AND Drive=’d:’

So I modified my code to adjust operators and variables that I use to build the filter string.


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

#it is possible the filename doesn't have an extension
if ($index -gt 0) {
#get the first part of the name
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)
}
else {
$filename=$Name
#will need to use wildcard search for filename when extension is empty
$fileop="LIKE"
$extension=$null
}
...
$filter = "Filename $fileOp '$filename' AND extension $extOp '$extension' AND Drive='$drive'"

Now I can find files without an extension, or with.


PS C:\> get-cimfile readme -drive d:

Compressed : False
Encrypted : False
Size :
Hidden : False
Name : d:\readme
Readable : True
System : False
Version :
Writeable : True
PSComputerName : SERENITY

PS C:\> get-cimfile readme.txt -drive d:

Compressed : False
Encrypted : False
Size :
Hidden : False
Name : d:\readme.txt
Readable : True
System : False
Version :
Writeable : True
PSComputerName : SERENITY

The last major change you’ll notice is that I build a hash table of parameter values and then splat it.


$paramHash=@{Classname="CIM_DATAFILE"}

Write-Verbose "Searching for $filename on drive $drive"
if ($pscmdlet.ParameterSetName -eq "Computername") {
Write-Verbose "...on $computername"
$paramHash.Add("Computername",$computername)
}
elseif ($pscmdlet.ParameterSetName -eq "CimSession") {
Write-Verbose "...on $Cimsession"
$paramHash.Add("CimSession",$cimSession)
}
...
#add the filter to the hashtable
$paramHash.Add("Filter",$filter)
...
Get-CimInstance @paramhash

This is a terrific technique when you are dynamically generating parameters.

Get-CimInstance can be used to query remote computers, assuming they are also running PowerShell 3.0. However, you can also use CIM Sessions which allow you to establish a connection to an older system using the DCOM protocol.


$sess = New-CimSession jdhit-dc01 -SessionOption (New-CimSessionOption -Protocol Dcom)

The end result is that I can still use my PowerShell 3.0 function to query a PowerShell 2.0 machine as long as I have a pre-created session.

get-cimfile3

Now I have a very powerful tool that can search just about any computer in my domain.

Oh, one more thing I realized in working on this. Initially I was only paying attention to the file name and version. Then I noticed that the Size property in the default output was always empty. That struck me as odd and not very useful. So I looked at the actual object with Get-Member and it turns out there is a FileSize property which is populated. It looks like the default property set for CIM_DATAFILE uses the Size property, when it should really be FileSize. So keep that in mind as you are working with the results.

Download Get-CIMFile3 and try it out for yourself.

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.