Quick and Dirty Reset of a Hyper-V VM with PowerShell

Note: Remember, this is a “Quick and Dirty” solution. In fact, because credentials are hard‐coded in the script, it is “Quick and Filthy”. But that tends to be the nature of IT‐Pro quick fixes, and I found it useful for a particular scenario. Just remember, this is merely being shown as an example and not a best practice.

Background: I had a problematic VM that would freeze over a period of time which required a “Hard Reset” to make it functional again. I wanted a way to reset the VM remotely, rather than from the console of the Hyper‐V parent.

The following steps will be taken to perform a hard reset on a VM:

  1. List ﴾enumerate﴿ the virtual machines on a Hyper‐V parent and identify the VM to reset by capturing its “Name” ﴾GUID﴿.
  2. Build a credential object that allows the execution of the RequestStateChange method ﴾typically Administrator credentials are required﴿.
  3. Execute the RequestStateChange method

List ﴾Enumerate﴿ the virtual machines on a Hyper‐V parent

PS> winrm enumerate wmi/root/virtualization/msvm_computersystem /r:<Hyper-V Parent> /u:<username> /p:<password>
     AssignedNumaNodeList = 0
     Caption = Virtual Machine
     CreationClassName = Msvm_ComputerSystem
     Description = Microsoft Virtual Machine
     ElementName = WSMANR2
     EnabledDefault = 2
     EnabledState = 2
     HealthState = 5
     Datetime = 20110725T20:31:41Z
     Name = C4E916BB92D54A4097C10664FCC0123B
     NameFormat = null
     OnTimeInMilliseconds = 5774786
     OperationalStatus = 2
     OtherEnabledState = null
     PrimaryOwnerContact = null
     PrimaryOwnerName = null
     ProcessID = 2064
     RequestedState = 12
     ResetCapability = 1
     Status = null
     StatusDescriptions = Operating normally
          Datetime = 20110822T17:33:46.726979Z
          Datetime = 20110822T17:33:46Z

Build a credential object ﴾this approach allows for the credentials to be hardcoded into a script, whereas the typical “get‐credential” cmdlet will not allow the password to be stored in the script and enforces the more secure method of manual entry of the password﴿.

PS> $password = convertto-securestring -String "<password>" -asplaintext -force
PS> $credential = new-object -typename System.Management.Automation.PSCredential -argumentlist "<username>",$password

Execute the RequestStateChange method on the VM ﴾acts like a Hard Reset for this example﴿

PS> # Various supported state changes: 2=Turns the VM on, 3=Turns the VM off, 10=A hard reset of the VM, 32768=Pauses the VM, 32769=Saves the state of the VM
PS> invoke-wsmanaction -action RequestStateChange -resourceuri wmi/root/virtualization/msvm_computersystem valueset @{RequestedState="10"} –selectorset @{Name="<VM Name/GUID from Above>"} -computername <HyperV Parent> -authentication default -credential $credential


Sample PowerShell 2.0 Remoting Commands

The following are a list of commands that I demonstrated at TechEd 2010 in New Orleans. Actually I should say that I intended to demo these commands, but wasn’t able to complete the entire list due to a conference wide network outage. :‐﴾

Many of these commands were intended to run against a real world web server in the Internet ﴾http://wsman.msft.net﴿. For one command ﴾WS‐Man ID﴿, I’ll include the web server in the command syntax. Otherwise I’ll just use “<server name>” to specify the destination. In addition, my demo server is configured to respond to WS‐Management from the original port 80 ﴾rather than the new port 5985 which was changed in WinRM 2.0﴿.

Note: If your client or end points are not Windows7 or Windows Server 2008 R2, then you will need to have PowerShell 2.0 installed on both systems. You can get the bits as Windows updates from http://download.microsoft.com.

Note: All commands are intended to be executed from PowerShell 2.0 or the PowerShell Integrated Scripting Environment (ISE).

Test WS‐Man connectivity without authentication.

This is useful for making sure the network and the Windows Remote Manage service are operational and intentionally does not check credentials since that is usually another level of configuration can be tested on its own.

test-wsman –computername wsman.msft.net:80 –authentication none

Create a credential token to be used throughout the remaining commands.

$cred = get-credential <administrator account name on end point>

Test WS‐Man connectivity with credentials ﴾note version info is now displayed﴿.

As mentioned above, it is helpful to be able to isolate authentication when troubleshooting management connectivity issues.

test-wsman -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Enumerate status for all Services.

This is merely using WS‐Man as the transport for accessing WMI providers. In the past, DCOM was the transport, but had many limitations due to its firewall unfriendly nature.

get-wsmaninstance -enumerate wmicimv2/win32_service -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Enumerate status for IIS Service.

This demonstrates getting the state of a specific service ﴾or element﴿ by using the “selectorset” parameter.

get-wsmaninstance wmicimv2/win32_service -selectorset @{name="w3svc"} -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Stop and Start the IIS Service.

Again, this is merely using WS‐Man as the transport in order to manipulate WMI methods that have been around since the dawn of time.

invoke-wsmanaction -action stopservice -resourceuri wmicimv2/win32_service -selectorset @{name="w3svc"} -computername <server name>:<port if other than 5985> -authentication default -credential $cred

This will verify the state of the stopped service.

get-wsmaninstance wmicimv2/win32_service -selectorset @{name="w3svc"} -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Now restart the IIS service.

invoke-wsmanaction -action startservice -resourceuri wmicimv2/win32_service -selectorset @{name="w3svc"} -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Store Output into an Object.

WMI instrumentation and actions are now very easy to automate with the addition of WS‐Man as a transport for remoting and PowerShell for scripting. The example here shows how the WMI information can be pulled into an object and properly formatted.

$operatingsystem = get-wsmaninstance -enumerate wmicimv2/win32_operatingsystem -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Show the output for the Last Boot Time for the end point.


Format this Boot Time data into a proper .Net DateTime object.


Query a VM Host

A good deal of Microsoft’s hypervisor’s ﴾Hyper‐V﴿ as well as VMWare’s hypervisor’s instrumentation and management is exposed via WMI. The following command displays characteristics of the Hyper‐V parent as well as all of its children.

get-wsmaninstance -enumerate wmi/root/virtualization/Msvm_computersystem -computername <server name>:<port if other than 5985> -authentication default -credential $cred

Create a persistent connection to a Remote System.

This is using WS‐Man to create a connection to the remote system, not PowerShell. Note that the port is not used in‐line with the “ComputerName”.

Connect-WSMan -computername <server name> -authentication default -credential $cred -port <port if other than 5985>

This will show the configuration of the remote system, including the listener.

cd wsman:

The connection does need to be ended.

disconnect-WSMan -computername wsman.msft.net

Create and use a PowerShell Remoting Session.

Using PowerShell for remote management is much more powerful as it allows for the scripts ﴾or script blocks﴿ to be passed within the connection rather than requiring them to exist on the remote computer.

Note: If the script or script block that is being passed to the remote computer is using any special modules, they will need to exist on the remote computer (modules are note passed with the script).

$wsman = new-pssession -computername <server name> -port <port if other than 5985> -authentication default -credential $cred

This merely shows how to remotely execute a single PowerShell command on a remote machine and that the output is returned as a formatted object with the remote machine’s meta data attached to the results.

invoke-command -session $wsman -scriptblock {getprocess}

We’ll then put the results into an object. Notice now nicely the data in the object is formatted. This is not the case when non‐PowerShell commands are executed remotely ﴾see below﴿.

$output = invoke-command -session $wsman -scriptblock {getprocess}

This shows how to tear down the remote session.

remove-pssession -session $wsman

Create and use a PowerShell Remoting Session on Several Servers

One of the most powerful features of PowerShell remoting is the ability to execute scripts on many servers simultaneously ﴾knows as “fan out”﴿. There is also the ability to throttle the number of servers that are simultaneously running scripts. The example below shows how to specify multiple servers within the command, but there are other ﴾more programmatic﴿ ways of doing this ﴾see “get‐help” for examples﴿.

$several = new-pssession -computername <server  name 1>,<server name 2>,<server name 3> -port <port if other than 5985> -authentication default -credential $cred
invoke-command -session $several -scriptblock {getprocess}
$output = invoke-command -session $several -scriptblock {getprocess}

The following example show how a “fan out” PowerShell command can also be executed in the background and monitored by using the “asjob” flag.

invoke-command -session $several -scriptblock {getprocess} asjob
receive-job -id <ID # listed from "getjob">

The following example shows how the output is formatted if the executed command is not a PowerShell script or cmdlet.

invoke-command -session $several -scriptblock {ipconfig /all}

When the results are placed in a PowerShell object, the object is essentially an array of single lines of text.

$output = invoke-command -session $several -scriptblock {ipconfig /all}
remove-pssession -session $several

Enter into a PSSession

The following examples show how to remote to a single end point and execute commands ﴾in this case the commands will stop and restart the web service﴿.

$wsman = new-pssession -computername <server name> -port <port if other than 5985> -authentication default -credential $cred
enter-pssession -session $wsman
net stop w3svc
net start w3svc
remove-pssession -session $wsman


Hyper-V Virtual Identification

Since virtual machines can be easily moved between physical hosts (parents), it becomes important to track where virtual machines are physically residing for both asset management as well as troubleshooting purposes. The following post focuses on discovering the relationship between virtual hosts (parents) and the virtual machines (children) from both the perspective of the parent as well as the perspective of the child.


Note: The following examples use the WinRM and WinRS command-line utilities which shipped with Windows Vista and Windows Server 2008, but are also available as an Out-Of-Band install for Windows XP SP2+ and Windows Server 2003 SP1+ here.

Query the Parent:

Most commonly used for asset collection, this model gathers the names (and other virtual machine characteristics) of all the children running on a virtual host. This method queries the Hyper-V specific WMI provider/class by using the following command.

winrm enumerate wmi/root/virtualization/msvm_computersystem /r:<remote Hyper-V Host>

With the following sample output:

     AssignedNumaNodeList = 0
     Caption = Virtual Machine
     CreationClassName = Msvm_ComputerSystem
     Description = Microsoft Virtual Machine
     ElementName = PROV-XP
     EnabledDefault = 2
     EnabledState = 2
     HealthState = 5
          Datetime = 2008-07-01T21:47:02Z
     Name = 31F497F1-2437-4E89-8308-BE07FB5C14C2
     NameFormat = null
     OnTimeInMilliseconds = 432464839
     OperationalStatus = 2
     OtherEnabledState = null
     PrimaryOwnerContact = null
     PrimaryOwnerName = OTTOH-HOST\Administrator
     ProcessID = 2628
     RequestedState = 12
     ResetCapability = 1
     Status = null
          Datetime = 2008-07-30T17:07:06Z
          Datetime = 2008-07-30T17:07:06Z

Query the Child:

Most commonly used for troubleshooting scenarios where a virtual machine is being evaluated and needs to be queried in order to determine its physical host (parent). The following command queries the registry on the child in order to determine its host (parent):


Remote Access Method #1 (the /f parameter merely structures the output in XML – handy for scripting, especially in PowerShell):

winrm invoke GetStringValue wmi/root/default/StdRegProv @{hDefKey="2147483650";sSubKeyName="Software\Microsoft\Virtual Machine\Guest\Parameters";sValueName="PhysicalHostNameFullyQualified"} /r:<Remote VM> /u:<Username> /p:<Password> /f:pretty

Remote Access Method #2:

winrs /r:<Remote VM> /u:<Username> /p:<Password> reg query "HKLM\Software\Microsoft\Virtual Machine\Guest\Parameters" /v PhysicalHostNameFullyQualified

Note: The first method demonstrates a powerful way to access the value of any registry key using the ‘StdRegProv’ WMI provider via WS-Man/WinRM for remote transport. Other registry hives can be accessed with the following hDefKey values: HKLM=2147483650, HKCU=2147483649, HKCR=2147483648, HKEY_USERS=2147483651.


Quick and Dirty Large Scale Eventing for Windows

One of the least known yet most powerful management features to ship with Windows Vista and Windows Server 2008 is built‐in Event Forwarding which enables large scale health and state monitoring of a Windows environment ﴾assuming health and state can be determined from Windows Events ‐which they usually can﴿. Not only is this feature built into the latest versions of Windows, but it’s also available for down‐level OSs like Windows XP SP2+ and Windows Server 2003 SP1+ ﴾here﴿.

Note: True enterprise class Windows eventing is included with enterprise monitoring solutions like System Center Operations Manager.

This new Windows Event Forwarding ﴾also known as Windows Eventing 6.0﴿ is exceptional for the following reasons:

  1. Standards Based: No really! It leverages the DMTF WS‐Eventing standard which allows it to interoperate with other WS‐Man implementations ﴾see OpenWSMAN at SourceForge﴿.
  2. Agentless: Event Forwarding and Event Collection are included in the OS by default
  3. Down‐Level Support: Event Forwarding is available for Windows XP SP2+ and Windows Server 2003 SP1+
  4. Multi‐Tier: Forwarding architecture is very scalable where a “Source Computer” may forward to a large number of collectors and collectors may forward to collectors
  5. Scalable: Event Collection is very scalable ﴾available in Windows Vista as well﴿ where the collector can maintain subscriptions with a large number of “Source Computers” as well as process a large number of events per second
  6. Group Policy Aware: The entire model is configurable by Group Policy
  7. Schematized Events: Windows Events are now schematized and rendered in XML which enables many scripting and export scenarios
  8. Pre‐Rendering: Forwarded Windows Events can now be pre‐rendered on the Source Computer negating the need for local applications to render Windows Events
  9. Resiliency: Designed to enable mobile scenarios where laptops may be disconnected from the collector for extended periods of time without event loss ﴾except when logs wrap﴿ as well as leveraging TCP for guaranteed delivery
  10. Security: Certificate based encryption via Kerberos or HTTPS

This implementation will walk through the following example design where via Group Policy a domain computer group will be configured to forwarded Windows Events to a single collector:


Implementation steps are as follows:

  • Step 1: Create Event Forwarding Subscription
  • Step 2: Configure WinRM Group Policy
  • Step 3: Configure Event Forward Group Policy
  • Step 4: Test

Step 1: Create the Event Forwarding Subscription on the Event Collector

In the Windows Event Forwarding architecture, the subscription definition is held and maintained on the Collector in order to reduce the number of touch‐points in case a subscription needs to be created or modified. Creating the subscription is accomplished through the new Event Viewer user interface by selecting the ‘Create Subscription’ action when the ‘Subscriptions’ branch is highlighted. The Subscription may also be created via the “WECUTIL” command‐line utility.

Note: Both Windows Vista and Windows Server 2008 can be event collectors ﴾this feature is not supported for down‐level﴿. Although there are no built‐in limitations when Vista is a collector, Server 2008 will scale much better in high volume scenarios.


Although the above subscription is configured to leverage Group Policy, the subscription can be configured in a stand‐alone mode ﴾see the “Collector Initiated” option﴿. In addition, this subscription is designed to gather all events from the “Application” and “System” logs that have a level of “Critical”, “Error”, or “Warning”. This event scope can be expanded to gather all events from these logs or even add additional logs ﴾like the “Security” log﴿.

Lastly, the subscription is configured to forward events as quickly as possible with the advanced settings delivery option of “Minimize Latency”. The default setting of “Normal” would only forward events every 15 minutes ﴾which may be more desirable depending the the Collector and Source Computer resources﴿.


If Group Policy is not being used, configure the “Subscription type” to be “Collector Initiated”. In this case Source Computers will need to be manually added to the Subscription either through the Subscription configuration or the “WECUTIL” command‐line utility ﴾which can also be scripted using PowerShell, but that’s another topic﴿.

Note: In cases where there Source Computer is generating a large volume of forwarded events ﴾e.g. Security events from a Domain Controller﴿, use WECUTIL on the collector to disable event rendering Quick and Dirty Large Scale Eventing for Windows Otto for the subscription. The task of pre‐rendering an event on the source computer can be CPU intensive for a large number of events.

Step 2: Configure Group Policy to enable Windows Remote Management on the Source Computers ﴾clients﴿

Group Policy can be used to enable and configure Windows Remote Management ﴾WinRM or WSMan﴿ on the Source Computers. WinRM is required by Windows Event Forwarding as WS‐Man is the protocol used by WS‐Eventing. The following shows the Group Policy branch locations for configuring both WinRM and Event Forwarding:


The following GP setting will enable WinRM on the client as well as configure a Listener that will accept packets from ANY source.


Note: This Listener configuration should only be used in a trusted network environment. If the environment is not trusted ﴾like the Internet﴿, then configure only specific IP Addresses or ranges in the IPv4 and IPv6 filters.

To configure WinRM outside of Group Policy, run the following command on the Source Computer ﴾also see the above Note﴿:

winrm quickconfig

Step 3: Configure Group Policy to enable Windows Event Forwarding on the Source Computers

As with WinRM, Group Policy can be used to configure Source Computers ﴾Clients﴿ to forward events to a collector ﴾or set of collectors﴿. The policy is very simple. It merely tells the Source Computer to contact a specific FQDN ﴾Fully Qualified Domain Name﴿ or IP Address and request subscription specifics. All of the other subscription details are held on the Collector.


If Group Policy is not being used, then there is nothing to do here as the “Collector Initiated” Subscription will proactively reach out to the Source Computer.

Step 4: Test Event Forwarding

If all of the Event Forwarding components are functioning ﴾and there’s minimal network latency﴿, a test event created on the Source Computer should arrive in the Collector’s “Forwarded Events” log within 60 seconds. Create a test event with the following command:

eventcreate /id 999 /t error /l application /d "Test event."


This event should appear on the Collector as follows:


If the event doesn’t appear, perform the following troubleshooting steps:

Troubleshooting Step 1: Has Policy Been Applied to the Source Computer?

This can be forced by running the following command on the Source Computer:

gpupdate /force
Troubleshooting Step 2: Can the Collector Reach The Source Computer via WinRM?

Run the following command on the Collector

winrm id /r:<Source Computer> /a:none
Troubleshooting Step 3: Is the Collector Using the Right Credentials?

Run the following command on the Collector

winrm id /r:<Source Computer> /u:<username> /p:<password>

Note: These are the credentials defined in the Subscription on the Collector. The credentials don’t need to be in the local Administrators group on the Source Computer, they just need to be in the “Event Log Readers” group on the Source Computer ﴾local Administrators will also work﴿.

Troubleshooting Step 4: Has the Source Computer Registered with the Collector?

Run the following command on the Collector

wecutil gr <subscription name>

This will list all the registered Source Computers ﴾note if the Subscription is “Collector Initiated” then this will list all configured Source Computers﴿, their state ﴾from the Collector’s perspective﴿, and their last heartbeat time.


Quick and Dirty Web Site Monitoring with PowerShell

The other day Mark noticed that redirections for our http://www.sysinternals.com/ URL were intermittently failing. In order to get more objective data, I built a script that tested the URL every 5 seconds, and reported back Success or Failure as well as performance ﴾how long it took to completely download base HTML content﴿. I found that PowerShell provided an easy way to use the WebClient .Net object and evaluate the returned HTML content.


Example 1: Single Site Monitoring

The following example opens a URL every 5 minutes, tests the content, and measures the time it took to download the HTML for the page. Notice that all the HTML is dumped into a big fat string. The string is then searched for specific text that is known to be in the requested page. Note that this script runs forever and can be stopped with a <Ctrl> ‘C’.

Example PowerShell script:
$webClient = newobject
$webClient.Headers.Add("useragent","PowerShell Script")
while (1 eq 1) {
   $output = ""
   $startTime = getdate
   $output = $webClient.DownloadString "http://www.sysinternals.com/")
   $endTime = getdate
   if ($output like "*Mark Russinovich*") {
      "Success`t`t" + $startTime.DateTime + "`t`t" + ($endTime $startTime).TotalSeconds + " seconds"
   } else {
      "Fail`t`t" + $startTime.DateTime + "`t`t" + ($endTime $startTime).TotalSeconds + " seconds"


Example 2: Monitoring and Alerting for Multiple Web Sites

This script monitors multiple URLs ﴾or web sites﴿, and incorporates e‐mail alerting and logging. Unlike the above script, it is designed to be triggered from the Windows Task Scheduler ﴾or some other job scheduler﴿ rather than Quick and Dirty Web Site Monitoring with PowerShell running forever in a loop. Notice that one of the URLs is actually a zipped file and PowerShell has no problem evaluating it as a string.

Example PowerShell script:
# Collects all named paramters (all others end up in $Args)
# Display Help
if (($Args[0] eq "?") or ($Args[0] eq "help")) {
   "Usage: SysinternalsSiteTest.ps1 alert <address> log"
   " alert <address> Send email alerts"
   " log Log results"
   "Example: SysinternalsSiteTest.ps1 alert somebody@nospam.com log"
# Create the variables
$global:GArgs = $Args

$urlsToTest = @{}
$urlsToTest["Sysinternals Redirect"] = "http://www.sysinternals.com"
$urlsToTest["TechNet Redirect"] = "http://www.microsoft.com/sysinternals"
$urlsToTest["Sysinternals Home"] = "http://www.microsoft.com/technet/sysinternals/default.mspx"
$urlsToTest["Sysinternals Forum"] = "http://forum.sysinternals.com"
$urlsToTest["Sysinternals Blog"] = "http://blogs.technet.com/sysinternals"
$urlsToTest["Sysinternals Downloads"] = "http://download.sysinternals.com/Files/NtfsInfo.zip"
$successCriteria = @{}
$successCriteria["Sysinternals Redirect"] = "*Mark Russinovich*"
$successCriteria["TechNet Redirect"] = "*Mark Russinovich*"
$successCriteria["Sysinternals Home"] = "*Mark Russinovich*"
$successCriteria["Sysinternals Forum"] = "*Sysinternals Utilities*"
$successCriteria["Sysinternals Blog"] = "*Sysinternals Site Discussion*"
$successCriteria["Sysinternals Downloads"] = "*ntfsinfo.exe*"
$userAgent = "PowerShell User"
$webClient = newobject System.Net.WebClient
foreach ($key in $urlsToTest.Keys) {
   $output = ""
   $startTime = getdate
   $output = $webClient.DownloadString($urlsToTest[$key])
   $endTime = getdate
   if ($output like $successCriteria[$key]) {
      $key + "`t`tSuccess`t`t" + $startTime.DateTime + "`t`t" + ($endTime $startTime).TotalSeconds + " seconds"
      if ($GArgs eq "log") {
         $key + "`t`tSuccess`t`t" + $startTime.DateTime + "`t`t" + ($endTime - $startTime).TotalSeconds + " seconds" >> WebSiteTest.log
   } else {
      $key + "`t`tFail`t`t" + $startTime.DateTime + "`t`t" + ($endTime $startTime).TotalSeconds + " seconds"
      if ($GArgs eq "log") {
         $key + "`t`tFail`t`t" + $startTime.DateTime + "`t`t" + ($endTime - $startTime).TotalSeconds + " seconds" >> WebSiteTest.log
      if ($alert) {
         $emailFrom = "computer@nospam.com"
         $emailTo = $alert
         $subject = "URL Test Failure " + $startTime
         $body = "URL Test Failure: " + $key + " (" + $urlsToTest[$key] + ") at " + $startTime
         $smtpServer = "somesmtpserver.nospam.com"
         $smtp = newobject Net.Mail.SmtpClient($smtpServer)



Quick and Dirty Software Inventory with PsInfo and PowerShell

PsInfo is great for gathering asset information from Windows computers, both locally and remotely. PowerShell is great for automation and cleaning up output (among other things) as well as working with database driven data.

The following examples show how to gather an itemized list of the installed software on remote machines, process the data, then either display it to the screen or store it in a database. It’s worth noting that PsInfo can also work on multiple remote computers from its native command line, or even read a list of computers from a file (check out the PsInfo site for more info). Since the final example seeks to show PsInfo in a database driven envoriment, PowerShell comes in very handy.

Note: In order for this example to work the necessary network connectivity and credentials will need to be in place.

Consider the following examples:

  1. The output is merely displayed on the screen. With this method the output can be redirected to a file and imported into an application like Excel for further analysis or record keeping.
  2. A database is used to drive the computers polled as well as store the output. The database table is very flat (one table) with 2 fields: ‘Computer’ and ‘Software’. For large amounts of data, this will need to be normalized.


With the following output (imported into Excel):


Example 1: Standard Screen Output

The following PowerShell script gathers a software inventory from 3 remote computers (‘happyhour’, ‘shaken’, and ‘extradry’). Presumably, your computer names will be different. After gathering and parsing the data, it’s then displayed on the screen for all machines successfully queried.

Before running this script, test your connectivity and credentials with a single PsInfo command:

PsInfo -s Applications \\somecomputer
Example PowerShell script:
$computersToQuery = ("happyhour","shaken","extradry")
$softwareInventory = @{}
foreach ($computer in $computersToQuery) {
  $psinfoOutput = ./psinfo.exe -s Applications \\$computer
  $foundSoftwareInventory = 0
  $computerName = ""
  foreach ($item in $psinfoOutput) {
    if ($foundSoftwareInventory -eq 1) {
      # Force the results to a string
      # Remove any single quotes which interfere with T-SQL statements
      # Load the result into a hash whereby removing any duplicates
      [string]$softwareInventory[$computerName][$item.Replace("'","")] = ""
    if ($item -like "System information for *") {
     $computerName = $item.Split("\")[2].TrimEnd(":")
    } elseif ($item -eq "Applications:") {
     $foundSoftwareInventory = 1
     $softwareInventory[$computerName] = @{}
foreach ($computer in $softwareInventory.Keys) {
  foreach ($softwareItem in $softwareInventory[$computer].Keys) {
   $computer + ":" + $softwareItem

Your output should look something like:


Example 2: Save Output to a Database

This example is additive to the first in that it adds the following 3 items:

  1. Pulls the list of computer to query from a database table
  2. Adds the current data and time to the result
  3. Records the audit results into a database

The following is the database schema for this example:


Example PowerShell script:
# Open the database connection
$dbConn = new-object System.Data.SqlClient.SqlConnection "server=kcdb;database=Inventory;Integrated Security=sspi"
$sqlQuery = $dbConn.CreateCommand()

# Get all known computers
$sqlQuery.CommandText = "select * from Inventory..Computers"
$reader = $sqlQuery.ExecuteReader()
$computersToQuery = @()
while ($reader.Read()) {
   $computersToQuery += $reader["Computer"]

# Close the database connection

$softwareInventory = @{}
foreach ($computer in $computersToQuery) {
   $psinfoOutput = ./psinfo.exe -s Applications \\$computer
   $foundSoftwareInventory = 0
   $computerName = ""
   foreach ($item in $psinfoOutput) {
      if ($foundSoftwareInventory -eq 1) {
         # Force the results to a string
         # Remove any single quotes which interfere with T-SQL statements
         # Load the result into a hash whereby removing any duplicates
         [string]$softwareInventory[$computerName][$item.Replace("'","")] = ""

      if ($item -like "System information for *") {
         $computerName = $item.Split("\")[2].TrimEnd(":")
      } elseif ($item -eq "Applications:") {
         $foundSoftwareInventory = 1
         $softwareInventory[$computerName] = @{}

$dbConn = new-object System.Data.SqlClient.SqlConnection "server=kcdb;database=Inventory;Integrated Security=sspi"
$sqlQuery = $dbConn.CreateCommand()
foreach ($computer in $softwareInventory.Keys) {
   foreach ($softwareItem in $softwareInventory[$computer].Keys) {
      "Loading-" + $computer + ":" + $softwareItem
      # Try an Insert than an Update
      trap {
         $sqlQuery.CommandText = "update Inventory..SoftwareInventory set AuditDate = getdate() where Computer = '" + $computer + "' and Software = '" + $softwareItem + "'"
         $result = $sqlQuery.ExecuteNonQuery()
      $sqlQuery.CommandText = "insert into Inventory..SoftwareInventory (      Computer,Software,AuditDate) values ('" + $computer + "','" + $softwareItem + "',getdate())"
      $result = $sqlQuery.ExecuteNonQuery()


For more information:


A Few Good Windows Remote Management Commands

In Vista and beyond, a lot of instrumentation, configuration, and utilization information is exposed via WS-Man. WS-Man (aka: WS-Management, Windows Remote Management, and WinRM) incorporates many features, but I like to think of it as the management protocol/framework of the future (look out SNMP!). What makes WS-Man so great is the fact that it’s all standards based, rides on HTTP/HTTPS (very firewall/NAT friendly), and packages its data in SOAP/XML packets (easy to shove into a database or use with a script).

Out of the box; Vista WS-Man exposes WMI information as well as Windows Remote Shell capabilities. What this means is that with WS-Man it’s much easier to get instrumentation from remote machines as well as use that info in scripts.

Here are some sample commands to play with. If you cannot get the ‘Test WS-Man…’ step to work, none of the steps following will work either (you’re probably not using the right credentials to access the remote machine). One more caveat, the remote commands work best on domain joined machines. For workgroup machines, the WinRM service needs additional configuration.

Quickly configure the WS-Man service (Run from an Elevated Command prompt):

winrm QuickConfig

Quickly delete the WS-Man listener (Run from an Elevated Command prompt):

winrm invoke Restore winrm/Config @{}

Display your machine’s basic hardware info:

winrm enumerate wmicimv2/Win32_ComputerSystem

Display your operating system properties:

winrm get wmicimv2/Win32_OperatingSystem

Output your OS info in XML:

winrm get wmicimv2/Win32_OperatingSystem -format:pretty

Ping WS-Man on a remote machine:

winrm id -auth:none -remote:<some machine>

Test WS-Man access to a remote machine**:

winrm id -remote:<some machine>

Grab a remote machine’s WS-Man config:

winrm get winrm/Config -r:<some machine>

Grab a remote machine’s CPU load:

winrm g wmicimv2/Win32_Processor?DeviceID=CPU0 -fragment:LoadPercentage -r:<some computer>

Grab a remote machine’s free memory:

winrm g wmicimv2/Win32_OperatingSystem -fragment:FreePhysicalMemory -r:<some computer>

Stop a service on a remote machine:

winrm invoke stopservice wmicimv2/Win32_Service?name=w32time -r:<some computer>

Start a service on a remote machine:

winrm invoke startservice wmicimv2/Win32_Service?name=w32time -r:<some computer>

Reboot a remote machine:

winrm invoke reboot wmicimv2/Win32_OperatingSystem -r:<some computer>

Run a command on a remote machine (this uses winrS, not winrM):

winrs -r:<some computer> ipconfig /all

Use PowerShell to grab the WS-Man Win32_OperatingSystem XML output (Run from PowerShell):

[xml]$osInfo = winrm get wmicimv2/Win32_OperatingSystem /format:pretty

Display the OS version property:


Display the last boot time:


Put free memory metric into an XML variable:

[xml]$freemem = cmd /c "winrm get wmicimv2/Win32_OperatingSystem -fragment:FreePhysicalMemory -f:pretty -r:<some computer>"

Display the free memory value:


Note: This step verifies that you have good connectivity to the remote machine, WS-Man is running and properly configured on the remote machine, AND you have the correct permissions to fully leverage WS-Man on the remote machine. If this step fails, it’s probably a permissions issue.


WS-Man (WinRM) Architecture

The following diagram shows a high-level overview of the WS-Man (WinRM) architecture. In the diagram the ‘Client’ is querying the ‘Server’ for WS-Man information. Note that HTTP.sys and WinHTTP support the HTTP(s) transport for WS-Man, not IIS. In addition, IIS (or another web publishing service) can co-exist with WS-Man and share port 80 (granted, in Windows 7 and beyond, the default ports are 5985 and 5986 for http and https respectively, although I still prefer to use 80 and 443).


Quickly configure the WS-Man service

Remember, this needs to be run from an ‘Elevated’ Command Prompt.

As you can see, this simple command does quite a bit. Please note every modification (hightlighted) since this might increase the attack surface of your computer. For example, Quick Config configures a listener that accepts connections from every network interface. This is probably not ideal for edge machines that connect to unsecure networks (like the Internet). In addition, this command only needs to be run once.


Quickly delete the WS-Man listener

Although this command only deletes all WinRM listeners, it effectively turns off any WS-Man communication to a machine.


Display your machine’s basic hardware info and operating system properties

Win32_ComputerSystem and Win32_OperatingSystem are common WMI classes and useful for asset information and configuration information as well as some utilization metrics.


Output your OS info in XML

XML output makes the data much easier for storing in a database or dealing with programmatically (like with a script). PowerShell makes this that much easier since it works VERY well with XML (see below for a sample PowerShell script).


Ping WS-Man on a remote machine and test authorization credentials

Testing WS-Man on a remote machine is very useful in troubleshooting lots of connectivity and configuration issues. When pinging WS-Man without ‘auth’, it allows for the testing of the connectivity and basic service configuration. Using the ‘auth’ parameter tests the necessary authorization. Generally the credentials need to be in the ‘Administrators’ group for ‘auth’ to work. In this case no credentials are provided so the current credentials are used (this can be over-ridden). Notice that the OS version is included when successfully using ‘auth’ to test WS-Man.


Grab a remote machine’s WS-Man config

Successfully completing this step pretty much insures that one has complete access to WS-Man on the remote computer.

Grab a remote machine’s CPU load, free memory, and restart a service

WS-Man allows for gathering WMI properties (reading and writing although we’re only reading in this example) as well as invoking methods (starting and stopping a service; as well as rebooting!). Notice the minimal return code for the invoke commands (0 = success).


Run a command on a remote machine (this uses winrS, not winrM)

WinRS is another utility that leverages WS-Man. WinRS allows for the execution of local, non-interactive command-line commands on a remote machine and returns the output. In other words, if the command can be run at the CMD prompt without any required input and it only accesses local resource (no network shares for example), then it will most likely work. There are ways to get around the ‘local resource’ issue, but that is out of scope for this blog.


Use PowerShell to grab instrumentation via WS-Man

PowerShell is great for consuming data from WS-Man since it works very well with XML. Notice that in the following example it’s easy to ‘surf’ an object in PowerShell. In this case the XML object ‘osInfo’ is displayed on its own, and then expanded to the ‘Win32_OperatingSystem’ branch.


A simple PowerShell script

The following script shows how easy it is to automate the collection of WS-Man information using PowerShell.

$machines = ("machine1","machine2","machine3")
foreach ($machine in $machines) {
  [xml]$osInfo = winrm get wmicimv2/Win32_OperatingSystem /format:pretty /r:$machine
  $machine + ": " + $osInfo.Win32_OperatingSystem.LastBootupTime.DateTime

More Info

For more information on WS-Man, please see the following articles: