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) }
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.
Finding 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.
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.
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.
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
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.
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.
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.
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.
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.
I love how easy it is to manage computers with Windows PowerShell. It is a great reporting tool, but often I find people getting locked into one approach. I’m a big believer in flexibility and re-use and using objects in the pipeline wherever I can. So I put together a PowerShell script that I can run every morning on my computer and get a quick summary about what is happening, or perhaps not happening. My script, MorningReport.ps1, relies on WMI to gather a variety of system information. By default it connects to the local host, but I’ve provided a computername parameter. I’m assuming current credentials are good enough for any remote system, but you can always add support for alternate credentials, assuming I don’t in some future version.
By default the script writes a custom object to the pipeline that contains all of the other WMI information like disk utilization, service status, and event logs. But I wanted flexibility and ease of use, so the script also supports parameters of -Text and -HTML. The former creates nicely formatted text suitable for printing or saving to a file. The latter creates HTML code from the original objects. I rely heavily on creating HTML fragments with ConvertTo-HTML and then assembling everything at the end. The script writes the HTML code to the pipeline so if you want to save results to a file, simply pipe to Out-File. I did this because there may be times when you want the “raw” HTML code. You might want to save the HTML and create an HTML mail message with Send-MailMessage. Or maybe further tweak the HTML before saving it to a file. Again, I didn’t want to lock myself in. Here’s the main part of the script.
#script internal version number used in output
[string]$reportVersion="1.0.8"
<#
define some HTML style
here's a source for HTML color codes
http://www.immigration-usa.com/html_colors.html
the code must be left justified
#>
$head = @"
$ReportTitle
"@
If ($computername -eq $env:computername) {
#local computer so no ping test is necessary
$OK=$True
}
elseIf (($computername -ne $env:computername) -AND (Test-Connection -ComputerName $computername -quiet -Count 2)) {
#not local computer and it can be pinged so proceed
$OK=$True
}
If ($OK) {
Try {
$os=Get-WmiObject Win32_operatingSystem -ComputerName $computername -ErrorAction Stop
#set a variable to indicate WMI can be reached
$wmi=$True
}
Catch {
Write-Warning "WMI failed to connect to $($computername.ToUpper())"
}
if ($wmi) {
Write-Host "Preparing morning report for $($os.CSname)" -ForegroundColor Cyan
#services
Write-Host "...Services" -ForegroundColor Cyan
#get services via WMI and group into a hash table
$wmiservices=Get-WmiObject -class Win32_Service -ComputerName $computername
$services=$wmiservices | Group State -AsHashTable
#get services set to auto start that are not running
$failedAutoStart=$wmiservices | Where { ($_.startmode -eq "Auto") -AND ($_.state -ne "Running")}
#NetworkAdapters
Write-Host "...Network Adapters" -ForegroundColor Cyan
#get NICS that have a MAC address only
$nics=Get-WmiObject -Class Win32_NetworkAdapter -filter "MACAddress Like '%'" -ComputerName $Computername
$nicdata=$nics | Foreach {
$tempHash=@{Name=$_.Name;DeviceID=$_.DeviceID;AdapterType=$_.AdapterType;MACAddress=$_.MACAddress}
#get related configuation information
$config=$_.GetRelated() | where {$_.__CLASS -eq "Win32_NetworkadapterConfiguration"}
#add to temporary hash
$tempHash.Add("IPAddress",$config.IPAddress)
$tempHash.Add("IPSubnet",$config.IPSubnet)
$tempHash.Add("DefaultGateway",$config.DefaultIPGateway)
$tempHash.Add("DHCP",$config.DHCPEnabled)
#convert lease information if found
if ($config.DHCPEnabled -AND $config.DHCPLeaseObtained) {
$tempHash.Add("DHCPLeaseExpires",($config.ConvertToDateTime($config.DHCPLeaseExpires)))
$tempHash.Add("DHCPLeaseObtained",($config.ConvertToDateTime($config.DHCPLeaseObtained)))
$tempHash.Add("DHCPServer",$config.DHCPServer)
}
New-Object -TypeName PSObject -Property $tempHash
}
#Event log errors and warnings in the last $Hours hours
$last=(Get-Date).AddHours(-$Hours)
#System Log
Write-Host "...System Event Log Error/Warning since $last" -ForegroundColor Cyan
$syslog=Get-EventLog -LogName System -ComputerName $computername -EntryType Error,Warning -After $last
$syslogdata=$syslog | Select TimeGenerated,EventID,Source,Message
else {
#can't ping computer so fail
Write-Warning "Failed to ping $computername"
}
As you can see, it is a lengthy script, but I’ve tried to include a fair amount of internal comments and documentation, so I won’t repeat it here, but I will touch on a few key points.
First, I’m embedding a style sheet directly in any HTML output so any files I create can stand alone. You could certainly modify the relevant sections and use the -CSSUri parameter with ConvertTo-HTML. Next, I use a combination of Test-Connection and Try/Catch to handle computers that are offline or I can’t access. This speeds up the script and makes it nicer for the script user. Assuming all is good, I create a number of variables that hold the WMI information I am interested in.
After everything is collected, then I can write output using an If/ElseIf statement depending on what parameters were passed. If no parameters were specified then a custom object is assembled and written to the pipeline.
else {
#Write data to the pipeline as part of a custom object
But perhaps you’d like to see this in action. Here’s a sample HTML SampleReport. In this report, there are no recent errors or warnings in the Application event log.
Or if you have a few minutes, here’s a short clip of the script in action.
I hope you find this a useful jumping off point for your own script, although I think I’ve already worked out information you are most likely interested in. Download MorningReport.ps1 and try it out for yourself.
I think you’ll like this. Normally, I prefer my PowerShell commands to write objects to the pipeline. But there’s nothing wrong with sending output directly to the console, as long as you know that the output is intended only for the screen. What I find frustrating is the use of Write-Host when really, pipelined objects would be better. But for today, I’m going to revel in the beauty of the console and create a colorized drive utilization graph. Continue reading →
I was poking around WMI the other day in PowerShell and was intrigued by the Win32_Share class. This is a great way to find out what items are shared on a server such as printers and folders, although it’s not limited to those types of resources. I thought this would make a useful function; one that you might use to audit a collection of computers. Continue reading →
Warning: file_get_contents(http://www.twylah.com/JeffHicks/widgets/trending_render?layout=vertical&widget_caption=) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 503 Service Temporarily Unavailable
in /home/jhicks/public_html/blog/wp-content/plugins/twylah-widget/twylah-widget.php on line 91