My commentary for Beginner Event 5 in the 2011 Scripting Games is now available. One item that seems to be missing on the ScriptingGuys site is my complete solution so I thought I would share it here, plus a variation.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
My sample solution is perhaps a little over-wrought for a beginner level script, but I wanted to demonstrate what I felt were some inportant scripting techiniques.
[cc lang="PowerShell"]
#Requires -version 2.0
#You need to collect the user name, computer name, domain name, and the operating system information
#from a number of computers. To make matters easier, you have decided to write the information to a
#text file. The text file needs to be saved as an ASCII file, and not as Unicode. You should include
#the date in which the information was gathered to determine reliability of the information.
#Design points
# Extra points for a script that will work against a remote machine
# Extra points for a script that will accept a Text file, CSV or other type of input of remote computer names
# Extra points for adding useful help information and comments
# Design points for clear easy to read code, and use of native Windows PowerShell cmdlets
Param (
[string]$File="computers.txt",
[string]$LogFile="ComputerReport.txt",
[string]$ErrorLog="failed.txt",
[Switch]$Append
)
Set-StrictMode -Version 2
#Verify list of computers exists
If (Test-Path -Path $File)
{
#if error log already exists, delete it so we get a new file
if (Test-Path -Path $ErrorLog)
{
Remove-Item -Path $ErrorLog
}
#retrieve computer names filtering out any blank lines
$Computers=Get-Content -Path $file | Where {$_}
Write-Host "Gathering computer information. Please wait..." -ForegroundColor Cyan
#initialize an array to hold computer information
$data=@()
#for each computer in the list
Foreach ($Computer in $Computers) {
#trim off any spaces
$Computer=$Computer.Trim()
#write the computername to the console as a status message
Write-Host $Computer.toUpper() -ForegroundColor Cyan
#ping the computer using Test-Connection. I'm saving the results to a variable
#so that I can get the IP address. I set the ErrorAction preference to SilentlyContinue
#to suppress error messages
$ping=Test-Connection -ComputerName $Computer -Count 2 -ErrorAction "SilentlyContinue"
if ($ping)
{
#if the computer is pingable get WMI information
Try
{
#attempt to retrieve WMI information and if there is an error catch it
$OperatingSystem=Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$ComputerSystem=Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -ErrorAction Stop
#only process if WMI queries were sucessful
if ($OperatingSystem -AND $ComputerSystem)
{
#create a custom object using a property hash table
#I'm including ServicePack information since that might come in handy
$data+=New-Object -TypeName PSObject -Property @{
User=$ComputerSystem.UserName
Commputer=$OperatingSystem.CSName
IPAddress=$ping[0].ipv4address.ToString()
Domain=$ComputerSystem.Domain
OS=$OperatingSystem.Caption
ServicePack=$OperatingSystem.ServicePackMajorVersion
Model=$ComputerSystem.Model
Reported=(Get-Date)
}
}
else
{
#otherwise record the failure in a log file
$Computer.ToUpper() | Add-Content -Path $ErrorLog
}
} #close Try
Catch
{
Write-Warning "There was a problem retrieving information from $($Computer.ToUpper())"
#write the exception message as a warning
Write-Warning $_.Exception.Message
#write the failed computername to a text file
$computer.ToUpper() | Add-Content -Path $ErrorLog
} #close Catch scriptblock
} #if pingable
else
{
#computer is not pingable
$Computer.ToUpper() | Add-Content -Path $ErrorLog
}
} #foreach
#measure how many computers were queried and save the count property to a variable
$count=($data | measure-object).Count
Write-Host "Successfully queried $Count computers." -ForegroundColor Cyan
#write the WMI information to a log file if $data has anything in it
if ($count -gt 0)
{
#if a file was specified then record information
if ($LogFile -AND $Append)
{
$data | Out-File -FilePath $LogFile -Encoding ASCII -Append
}
else
{
$data | Out-File -FilePath $LogFile -Encoding ASCII
}
} #if $count greater than 0
Write-Host "See $LogFile for details and $ErrorLog for failed computers." -ForegroundColor Cyan
} #if file exisits
else
{
#Failed to verify text file of computers
Write-Warning "Cannot find or verify $file."
}
#end of script
[/cc]
I also wrote an "advanced" version with comment based help and one that accepts pipelined input. I've ommitted help section here but it is in the file you can download at the end.
[cc lang="PowerShell"]
Function Get-ComputerInfo {
[cmdletBinding()]
Param (
[Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullorEmpty()]
[Alias("name")]
[string[]]$Computername=$env:Computername,
[Parameter(Position=1,Mandatory=$False,HelpMessage="Enter the full filename and path for your report.")]
[string]$FilePath,
[switch]$Append,
[string]$ErrorLog="Failed.txt"
)
Begin
{
Set-StrictMode -Version 2
Write-Verbose "$(Get-Date) Starting script"
Write-Host "Gathering computer information. Please wait..." -ForegroundColor Cyan
#initialize an array to hold computer information
$data=@()
} #close Begin scriptblock
Process
{
#process the array of computernames either piped in or passes as parameter values
Foreach ($computer in $computername) {
Try
{
Write-Verbose "$(Get-Date) Getting information from $($Computer.toUpper())"
#write a progress message on the console if a file path was specified
#otherwise nothing is displayed which for a long running command migh
#lead the user to think something is wrong. When a file is not used each
#computer is written to the console
if ($Filepath)
{
Write-Host $Computer.toUpper() -ForegroundColor Cyan
}
#attempt to retrieve WMI information and if there is an error catch it
$OperatingSystem=Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
$ComputerSystem=Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -ErrorAction Stop
#only process if WMI queries were sucessful
if ($OperatingSystem -AND $ComputerSystem)
{
#create a custom object using a property hash table
#I'm including ServicePack information since that might come in handy
$object=New-Object -TypeName PSObject -Property @{
User=$ComputerSystem.UserName
Commputer=$OperatingSystem.CSName
Domain=$ComputerSystem.Domain
OS=$OperatingSystem.Caption
ServicePack=$OperatingSystem.ServicePackMajorVersion
Model=$ComputerSystem.Model
Reported=(Get-Date)
}
if ($FilePath)
{
#add the object to the data array in case we want to save it to a file
$data+=$object
}
else
{
#Otherwise write the new object to the pipeline
Write-Output $object
}
}
else
{
#display a warning if the WMI information is not present. This should never really
#be reached.
Write-Warning "There was an unknown problem. Not all information was available."
}
} #close Try scriptblock
Catch
{
Write-Warning "There was a problem retrieving information from $($Computer.ToUpper())"
#write the exception message as a warning
Write-Warning $_.Exception.Message
#write the failed computername to a text file
$computer.toUpper() | Add-Content -Path $ErrorLog
} #close Catch scriptblock
} #close Foreach
} #close Process script block
End
{
#if a file was specified then record information
if ($FilePath -AND $Append)
{
Write-Verbose "$(Get-Date) Appending to $filepath"
$data | Out-File -FilePath $Filepath -Encoding ASCII -Append
}
elseif ($FilePath)
{
Write-Verbose "$(Get-Date) Writing data to $filepath"
$data | Out-File -FilePath $Filepath -Encoding ASCII
}
Write-Verbose "$(Get-Date) Ending script"
} #close End scriptblock
} #end Function
#end of script
[/cc]
Fundamentally, the code hasn't really changed except for the way I handle pipelined objects and the use of Write-Verbose to display status messages.
Download Beg_5_2011.ps1 and Beg_5_2011_Variation.ps1.
This assumes you enable the Firewall exceptions for ICMP and WMI. We don’t enable ICMP, but WMI is enabled.
Beautiful solution none the less. Appreciate this!
Yes, that is a valid assumption and concern. Even though Test-Connection is using the WMI win32_pingstatus class, I believe it still requires ICMP.
Hey thanks for posting this. I was having quite a time debugging. (Not something I’m good at)
I’ll post here if I have any other questions.
Thanks again