Tag Archives: ScriptBlock

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:\> $os.properties | 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 {$_.properties | 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=$os.properties | 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[]]($os.properties | 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

Name
----
BootDevice
BuildNumber
BuildType
Caption
CodeSet
CountryCode
...

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"
BiosCharacteristics
BIOSVersion
BuildNumber
Caption
CodeSet
CurrentLanguage
...

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.

Friday Fun: 13 More Scriptblocks

In celebration of Friday the 13th I thought I would offer up a menu of 13 more script blocks. If you missed the first course, you can find the original 13 scrptblocks here. I’m not going to spend a lot of time going over these. Many of them are simple one liners. Some of them take parameters just like functions and scripts. The easiest way to execute any of the scriptblocks is to use the & operator. But these might also come in handy with any cmdlet that takes a scriptblock as a parameter value such as Invoke-Command.

I think of scriptblocks as “quick and dirty” blocks of re-usable code. If you find something very useful, you might expand it into a full-blown function complete with error handling and verbose output. Or you might find a handy technique in one of these examples.


#1 Get top problem source from the last 500 event log entries
$topprob={Param($log="System") Get-EventLog -LogName $log -newest 500 -entrytype Error |
Group Source -NoElement | Sort Count | Select -last 1}
#&$topprob

#2 Get folder usage by owner in MB
$usage={Param($path=".") dir $path -recurse | Where {-Not $_.PSIsContainer} |
Select Fullname,Length,@{N='Owner';E={($_ | Get-ACL).Owner}} |
Group Owner | Sort Count -descending|
Select Count,Name,@{N='SizeMB';E={(($_.Group | Measure length -sum).sum)/1MB}}
}
#&$usage

#3 get empty event logs
$emptylog={get-eventlog -list | where {$_.entries.count -eq 0}}
#&$emptylog

#4 Get OS Install date
$install={Param($Computername=$env:computername) Get-WmiObject win32_operatingsystem -comp $computername |
select CSName,Caption, @{N="Install";E={$_.ConvertToDateTime($_.InstallDate)}}}
#&$install

#5 Test if running Windows 8
$test8={Param($Computername=$env:computername)
(Get-WmiObject win32_operatingsystem -comp $computername).Caption -match "Windows 8"
}
#&$test8 MyWin8

#6 Test if running PowerShell v3
$testPS3={Param($Computername=$env:computername)
(test-wsman -ComputerName $computername).Productversion -match "Stack: 3.0"}
#&$testPS3 MyWin8

#7 Get code snippets from help examples
$excode={Param($command="get-service") (get-help $command).examples.example | select code}
#&$excode get-process

#8 Count by 13
$countup={Param($count=5) $x=0; For ($i=0;$i -lt $Count; $i++) {$x+=13;$x}}
#&$countup 13

#9 get a 13 character random password
$randpass={ Param($length=13) $chars=[char[]](33..126) ; -join ($chars | get-random -count $length)}
#&$randpass

#10 Test if profile scripts exist
$profileCheck={
$profile | Select @{N="Type";E={"AllUsersAllHosts"}},@{N="Path";E={$_.AllUsersAllHosts}},@{N="Exists";E={Test-Path $_.AllUsersAllHosts}}
$profile | Select @{N="Type";E={"AllUsersCurrentHost"}},@{N="Path";E={$_.AllUsersCurrentHost}},@{N="Exists";E={Test-Path $_.AllUsersCurrentHost}}
$profile | Select @{N="Type";E={"CurrentUsersAllHosts"}},@{N="Path";E={$_.CurrentUserAllHosts}},@{N="Exists";E={Test-Path $_.CurrentUserAllHosts}}
$profile | Select @{N="Type";E={"CurrentUserCurrentHost"}},@{N="Path";E={$_.CurrentUserCurrentHost}},@{N="Exists";E={Test-Path $_.CurrentUserCurrentHost}}
}
#&$profileCheck | format-list

#11 get default printer
$defaultPrint={get-wmiobject win32_printer -filter "Default='True'"}
#&$defaultPrint

#12 get timezone
$tz={Param($computername=$env:computername) Get-WmiObject win32_timezone -computername $computername |
Select @{N="Computername";E={$_.__SERVER}},Description}
#&$tz

#13 find expired certificates
$expired={dir cert:\ -recurse | where {$_.NotAfter -AND $_.NotAfter -lt (Get-date)}}
#&$expired
#Invoke-command $expired -comp server01

Download the script file and watch out for black cats under ladders!

PowerShell Scripting with [ValidateScript]

The last few days we’ve been looking at parameter validation attributes you might use in a script of function. Yesterday I wrote about [ValidateRange] and demonstrated how you might use it. That attribute works fine for any values that can be evaluated as numbers. But dates are a different story. I got a comment with a suggestion for validating dates and at first glance it appears to work. For a very small range it might, but here’s the code I was testing with.


Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a date",ValueFromPipeline=$True)]
[ValidateRange("1/1/2012","4/1/2012")]
$Date
)

Process {
write-host $date -ForegroundColor Green
}

I’m expecting date between 1/1/2012 and 4/1/2012. Anything else should throw an exception. Now watch what happens as I pipe some values to this:


PS S:\> "2/12/2012","5/1/2012","12/1/2011","13/2/2012" | .\Demo-ValidateRange-Da
te.ps1
2/12/2012
C:\scripts\Demo-ValidateRange-Date.ps1 : Cannot validate argument on parameter
'Date'. The 5/1/2012 argument is greater than the maximum allowed range of 4/1/
2012. Supply an argument that is less than 4/1/2012 and then try the command ag
ain.
At line:1 char:79
+ "2/12/2012","5/1/2012","12/1/2011","13/2/2012" | .\Demo-ValidateRange-Date.ps
1 <<<<
+ CategoryInfo : InvalidData: (5/1/2012:String) [Demo-ValidateRan
ge-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateRa
nge-Date.ps1

12/1/2011
13/2/2012

Values that are outside the range and even those that are bogus still are accepted. If I cast $date to [DateTime] which is what I would normally do, then nothing works at all. But that's fine because there is another attribute that is better suited to this task: [ValidateScript()].

To use this attribute we'll insert a scriptblock inside the parentheses. The scriptblock can be as complicated as you need it to be, but it must evaluate to either True or False. Use $_ to indicate the parameter value. So using my datetime example, I might use a validation script like this:


Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a date",ValueFromPipeline=$True)]
[ValidateScript( {
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
}
)]
[datetime]$Date
)

Process {
write-host $date -ForegroundColor Green
}

With a validation script I have much more flexibility. Now look at the results:


PS S:\> "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-Vali
dateScript-Date.ps1 | clip
2/12/2012 12:00:00 AM
C:\scripts\Demo-ValidateScript-Date.ps1 : Cannot validate argument on parameter
'Date'. The "
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
" validation script for the argument with value "5/1/2012 12:00:00 AM" did not
return true. Determine why the validation script failed and then try the comma
nd again.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<< | clip
+ CategoryInfo : InvalidData: (5/1/2012:String) [Demo-ValidateScr
ipt-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateSc
ript-Date.ps1

3/15/2012 12:00:00 AM
C:\scripts\Demo-ValidateScript-Date.ps1 : Cannot validate argument on parameter
'Date'. The "
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
" validation script for the argument with value "12/1/2011 12:00:00 AM" did no
t return true. Determine why the validation script failed and then try the comm
and again.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<< | clip
+ CategoryInfo : InvalidData: (12/1/2011:String) [Demo-ValidateSc
ript-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateSc
ript-Date.ps1

C:\scripts\Demo-ValidateScript-Date.ps1 : The input object cannot be bound to a
ny parameters for the command either because the command does not take pipeline
input or the input and its properties do not match any of the parameters that
take pipeline input.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<<
+ CategoryInfo : InvalidArgument: (13/2/2012:String) [Demo-Valida
teScript-Date.ps1], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Demo-ValidateScript-Date.ps1

The valid dates pass, dates outside the range fail the validation test and the last value which isn't a legal date also fails but with a slightly different error message. As with all of the validation attributes I could have inserted this code into the body of my script and thrown my own errors. That choice is up to you. [ValidateScript()] isn't difficult to use. Just remember to insert your commands into a scriptblock, use $_ for the parameter value, and make sure the scriptblock writes either $True or $False.