Category Archives: Windows Server 2008

Reporting On Installed Windows Programs Via The Registry

Quite a common request for working with Windows machines is to report the software installed on them. If you don’t have a centralised system for reporting on client software (many places don’t) then you may turn to some form of scripted method to obtain this information.

Most people tend to head to Add / Remove Programs when thinking about what software is installed in Windows. However, not all applications will always populate information in there, depending on how they have been installed. Additionally, to query that information you would typically query the WMI class Win32_Product, however this can lead to performance issues.

A better approach if going down this route is to look in the registry and the key  HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall.

The following function illustrates how to access this data by using this .NET approach to retrieve registry data. Note: this also works with remote computers.

[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('RegistryHive','ComputerName')

We check for the DisplayName on each subkey before adding the result to the output, since for some reason some keys are empty.

$DisplayName = $SubKeyValues.GetValue('DisplayName')<br />if ($DisplayName){........

Here’s the function which you could flesh out further for your own requirements.

Update 03/02/2014: As Peter points out in the notes, if you have 32bit software installed you have an extra place to check when using this method, so you will also need to make the same checks in the following key:

HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

function Get-InstalledSoftware {
<#
 .SYNOPSIS
 Retrieve installed software from the registry

 .DESCRIPTION
 Retrieve installed software from the registry

 .PARAMETER ComputerName
 Name of the computer to check

.INPUTS
 System.String

.OUTPUTS
 System.Management.Automation.PSObject.

.EXAMPLE
 PS> Get-InstalledSoftware -Computer Server01

 .EXAMPLE
 PS> "Server01","Server02" | Get-InstalledSoftware

#>
[CmdletBinding()][OutputType('System.Management.Automation.PSObject')]

Param
 (

[parameter(Mandatory=$true,ValueFromPipeline=$true)]
 [ValidateNotNullOrEmpty()]
 [String[]]$ComputerName

)

begin {

 $OutputObject = @()
 }

 process {

try {
 foreach ($Computer in $ComputerName){

 $Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Localmachine',$Computer)
 $UninstallTopKey = $Registry.OpensubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall",$false)
 $UninstallSubKeys = $UninstallTopKey.GetSubKeyNames()

ForEach ($SubKey in $UninstallSubKeys){

 $Path = "Software\Microsoft\Windows\CurrentVersion\Uninstall\$SubKey"
 $SubKeyValues = $Registry.OpensubKey($Path,$false)

$DisplayName = $SubKeyValues.GetValue('DisplayName')

if ($DisplayName){

 $Object = [pscustomobject]@{

 ComputerName = $Computer
 DisplayName = $DisplayName
 DisplayVersion = $SubKeyValues.GetValue('DisplayVersion')
 UninstallString = $SubKeyValues.GetValue('UninstallString')
 Publisher = $SubKeyValues.GetValue('Publisher')
 InstallDate = $SubKeyValues.GetValue('InstallDate')

}
 }

 $OutputObject += $Object

}
 }
 }
 catch [Exception]{

 throw "Unable to get registry data....."
 }
 }
 end {
 Write-Output $OutputObject
 }
}

Using PowerShell To Check That Windows Server Services Set To Automatic Have Started

Following on from the blog post Testing TCP Port Response from PowerShell  which provided a means to check that servers had fully rebooted after a patching and reboot cycle, I needed to take this one step further and check that all of the Windows Services set to Automatic successfully started after the reboot.

This should be pretty straightforward since we have a Get-Service cmdlet. Unfortunately however, this cmdlet does not return a StartMode parameter, i.e. it’s not possible to tell whether the Startup Type has been set to Automatic, Manual or Disabled. This is quite a large gap in my opinon – if you agree with me you can vote to get it included in a future release here. Of course with PowerShell there’s usually another way to achieve the same objective and using Get-WMIObject it is possible to find out the Startup Type of the service.

Get-WmiObject Win32_Service -ComputerName $ComputerName -Filter "StartMode='Auto' AND State='Stopped' AND Name!='SysmonLog'"

Notice that we filter out the Perfmon service (SysmonLog) since it is rarely in a started state.

One other thing to watch out for in this script is that the section

catch [System.Exception]

which is there to catch any WMI queries that fail, e.g. the server hasn’t rebooted properly or the correct permissions do not exist to make the WMI query, will not pick up any of these failures. This is because try / catch will only catch terminating errors and the WMI failures are non terminating. We can work around this by setting:

$ErrorActionPreference = "Stop"

and then back to normal afterwards:

$ErrorActionPreference = "Continue"

The script accepts pipeline input, so for example you could run it like:

Get-Content servers.txt | ./Get-AutomaticServiceState.ps1

Here it is:

<#
.SYNOPSIS
Retrieves any Windows services set to Automatic and are not running

.DESCRIPTION
Retrieves any Windows services set to Automatic and are not running

.PARAMETER  ComputerName
Name of the computer to test the services for

.EXAMPLE
PS C:\> Get-AutomaticServiceState -ComputerName Server01

.EXAMPLE
PS C:\> Get-Content servers.txt | Get-AutomaticServiceState

.NOTES
Author: Jonathan Medd
Date: 11/01/2012
#>

[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=$true,HelpMessage="Name of the computer to test",
ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true)]
[Alias('CN','__SERVER','IPAddress','Server')]
[System.String]
$ComputerName
)

process {

try {

# Set ErrorActionPreference to Stop in order to catch non-terminating WMI errors
$ErrorActionPreference = "Stop"

# Query the server via WMI and exclude the Performance Logs and Alerts Service
$WMI = Get-WmiObject Win32_Service -ComputerName $ComputerName -Filter "StartMode='Auto' AND State='Stopped' AND Name!='SysmonLog'"

}

catch [System.Exception]

{
$WMI = “” | Select-Object SystemName,Displayname,StartMode,State
$WMI.SystemName = $ComputerName
$WMI.Displayname = "Unable to connect to server"
$WMI.StartMode = ""
$WMI.State = ""
}

finally {

$ErrorActionPreference = "Continue"

}

if ($WMI){

foreach ($WMIResult in $WMI){

$MYObject = “” | Select-Object ComputerName,ServiceName,StartupMode,State
$MYObject.ComputerName = $WMIResult.SystemName
$MYObject.ServiceName = $WMIResult.Displayname
$MYObject.StartupMode = $WMIResult.StartMode
$MYObject.State = $WMIResult.State
$MYObject
}
}
}

Managing Perfmon Data Collector Sets with PowerShell

Whilst working with Microsoft Support on a performance case we needed to supply them with some Perfmon logs. To do so required creating a User Defined Data Collector Set containing a number of counters. You can obviously do this through the GUI, which is fine for one server, but we needed the logs from around 200 servers.

So we created a Data Collector Set on one server and then saved it out to a template which stores the data in an XML file. I then put together a script which read in the XML file and then used that data to create a Data Collector Set via the COM object Pla.DataCollectorSet. (There are currently no built-in Powershell cmdlets for doing this)

Since all of these servers were enabled for PowerShell remoting it was then very easy to execute that script on all 200 servers using Invoke-Command. I had issues reading in the XML file from a network location in my environment, so it first of all copies the file locally and removes it at the end. Note: it is important that you name the Data Collector Set the same as you did in the template, this allows for easier removal later on.

The resultant script is below. Most of the code around it is some basic error checking and using some of PowerShell 2.0’s built-in help and advanced function features. The key part that creates the Data Collector Set is here:

$datacollectorset = New-Object -COM Pla.DataCollectorSet
 $xml = Get-Content C:\temp\PerfmonTemplate.xml
 $datacollectorset.SetXml($xml)
 $datacollectorset.Commit("$DataCollectorName" , $null , 0x0003) | Out-Null
 $datacollectorset.start($false)

And the full script…….

<#
.SYNOPSIS
 Create a New Perfmon Data Collector Set from an XML input
.DESCRIPTION
 Create a New Perfmon Data Collector Set from an XML input
 Use PowerShell remoting to create these on a remote server.
 Remoting must be enabled on target servers
.NOTES
 Authors:  Jonathan Medd
.PARAMETER CSVFilePath
 Path of CSV file to import
.PARAMETER XMLFilePath
 Path of XML file to import
.PARAMETER DataCollectorName
 Name of new Data Collector. This should match the name in the XML file
.EXAMPLE
 New-DataCollectorSet -CSVFilePath C:\Scripts\Servers.csv -XMLFilePath C:\Scripts\PerfmonTemplate.xml -DataCollectorName CPUIssue
#>
param (
 [parameter(Mandatory=$True,HelpMessage='Path of CSV file to import')]
 $CSVFilePath
 ,
 [parameter(Mandatory=$True,HelpMessage='Path of XML file to import')]
 $XMLFilePath
 ,
 [parameter(Mandatory=$True,HelpMessage='Name of new Data Collector')]
 $DataCollectorName
 )

# Test for existence of supplied CSV and XML files
if (Test-Path $CSVFilePath){
 }
else{
 Write-Host "Path to CSV file is invalid, exiting script"
 Exit
 }
if (Test-Path $XMLFilePath){
 }
else{
 Write-Host "Path to XML file is invalid, exiting script"
 Exit
 }

# Generate list of servers to create Perfmon Data Collector Sets on
$servers = Get-Content $CSVFilePath

foreach ($server in $servers){

Write-Host "Creating Data Collector Set on $Server"

# Test if the folder C:\temp exists on the target server
if (Test-Path "\\$server\c$\Temp"){

 # Copy the XML file to the target server
 Copy-Item $XMLFilePath "\\$server\c$\Temp"

 # Use PowerShell Remoting to execute script block on target server
 Invoke-Command -ComputerName $server -ArgumentList $DataCollectorName -ScriptBlock {param($DataCollectorName)

 # Create a new DataCollectorSet COM object, read in the XML file,
 # use that to set the XML setting, create the DataCollectorSet,
 # start it.
 $datacollectorset = New-Object -COM Pla.DataCollectorSet
 $xml = Get-Content C:\temp\PerfmonTemplate.xml
 $datacollectorset.SetXml($xml)
 $datacollectorset.Commit("$DataCollectorName" , $null , 0x0003) | Out-Null
 $datacollectorset.start($false)
 }

 # Remove the XML file from the target server
 Remove-Item "\\$server\c$\Temp\PerfmonTemplate.xml"
 }
else{
 Write-Host "Target Server does not contain the folder C:\Temp"
 }
}

We calculated that this script would save 29 hours manual effort…….and that was just the first time we needed to do it. Later on we had to change some of the counters, so needed to first remove all of the Data Collector Sets and replace them with modified versions from a new XML file.

The following script removes an existing Data Collector Set and renames the folder it was storing logs in – this is because if you subsequently create a new one it will fail if you are using the same paths.

<#
.SYNOPSIS
 Remove a Specified Data Collector
.DESCRIPTION
 Remove a Specified Data Collector
.NOTES
 Authors:  Jonathan Medd
.PARAMETER CSVFilePath
 Path of CSV file to import
.PARAMETER DataCollectorName
 Name of existing Data Collector
.EXAMPLE
 Remove-DataCollectorSet -CSVFilePath C:\Scripts\Servers.csv -DataCollectorName CPUIssue
#>
param (
 [parameter(Mandatory=$True,HelpMessage='Path of CSV file to import')]
 $CSVFilePath
 ,
 [parameter(Mandatory=$True,HelpMessage='Name of existing Data Collector')]
 $DataCollectorName
 )

# Test for existence of supplied CSV file
if (Test-Path $CSVFilePath){
 }
else{
 Write-Host "Path to CSV file is invalid, exiting script"
 Exit
 }

# Generate list of servers to remove Perfmon Data Collector Sets from
$servers = Get-Content $CSVFilePath

# Create a string with the current date to append to the Perfmon folder name
$today = Get-Date
$RenameText = [string]$today.Day + [string]$today.Month + [string]$today.Year
$NewnameText = $DataCollectorName + '_' + $RenameText

foreach ($server in $servers){

Write-Host "Removing Collector Set for $Server"

# Use PowerShell Remoting to execute script block on target server
Invoke-Command -ComputerName $server -ArgumentList $DataCollectorName,$NewnameText,$Server -ScriptBlock {param($DataCollectorName, $NewnameText, $Server)

if (Test-Path C:\PerformanceLogs\$DataCollectorName) {

 $datacollectorset = New-Object -COM Pla.DataCollectorSet
 $datacollectorset.Query("$DataCollectorName",$null)
 $datacollectorset.stop($false)

 Start-Sleep 10

 $datacollectorset.Delete()

 Rename-Item -Path C:\PerformanceLogs\$DataCollectorName -NewName C:\PerformanceLogs\$NewnameText
 }
else {

 Write-Host "Collector Set for $Server Does Not Exist"

 }
}

}

Windows Server 2008 – Extend System Volume Error ‘The parameter is incorrect’

I previously blogged about Extending the System Disk in Windows Server 2008 where as a new feature over Windows Server 2003 it is possible to extend the system volume online and without needing to resort to third party tools which often required at least a reboot.

I’ve now used this feature many times, but yesterday I had an issue when extending the volume of a Windows Server 2008 system drive where it returned the horrible looking error below ‘The parameter is incorrect’.

This leaves you in a scenario where the Disk Management tool reports that the disk has been increased to the new size, but viewing the disk properties in Explorer shows it has not been increased.

Nicholas Schoonover has a post detailing the same issue and suggests fixing it by first shrinking the disk, then extending it again. There are also comments on that post which suggest that extending it further might also resolve it. I wasn’t particularly keen to try out either of those suggestions immediately so did a bit more research.

It turns out this has been an issue within Windows Server for sometime and this Microsoft KB article details how to resolve it with the Diskpart utility, essentially the disk partition has been increased, but not the file system size.

It’s as simple as

DISKPART> select volume #

where # is the number of the affected volume which can be found with list volume.

DISKPART> extend filesystem

Now the file system size should match the new partition size.

Tivoli Monitoring, WMI and Server Buffers Full

If you run Tivoli Monitoring 6.2 to monitor Windows Server systems and use other applications to query WMI, e.g. PowerShell and Get-WmiObject, then you may receive the error ‘Server buffers are full and data cannot be accepted’.

Restarting the WMI service will temporaily clear it, but the issue is liable to come back again. This can occur because of a file handle leak in the ITM Windows OS agent when collecting “Processor Information” attribute group.

There is a fix for this issue available from the below website.

http://www-01.ibm.com/support/docview.wss?rs=2292&context=SSRM2J&dc=DB550&uid=swg1IZ51505&loc=en_US&cs=UTF-8&lang=en&rss=ct2292tivoli

PowerShell ISE on Windows Server 2008 – what version of .NET is required?

After installing the Windows Management Framework, a.k.a PowerShell 2.0, on my test Windows 2008 64 bit Server I fired up the new PowerShell ISE tool and was prompted with this error:

ISE351

I already knew that PowerShell ISE had a higher dependency on .NET than PowerShell itself which only requires .NET 2.0, however I was curious about the statement in the above message which states:

“If you are running Windows Server 2008, you must use Server Manager to install or configure “.NET Framework 3.5.1”

I knew on my test Windows Server 2008 box that in Server Manager I only had the option to install .NET Framework 3.0, not 3.5.1.

NET30

Anyway, I duly installed .NET 3.0 through Server Manager to see what the effect would be on PowerShell ISE.

NET30Succeed

After the successful installation of .NET 3.0 I fired up the very useful tool ASoft .NET Version Detector 2007 to confirm what versions of of .NET where installed on the machine. (I had already checked in C:\Windows\Microsoft.NET , but ran this tool just to make doubly sure I wasn’t missing something.)

NETCheck

So I had .NET 2.0 SP2 and 3.0, but not 3.5.1. I fired up PowerShell ISE and low and behold it opened.

PowerShellISE

So my question is does PowerShell ISE require .NET 3.5.1 or only .NET 3.0? Answers on a postcard or comments on the blog please!

Update:

Thanks to Aleksandar who pointed out that the answer is in PowerShell help itself and that only .NET 3.0 is required.

Get-Help about_Windows_PowerShell_ISE

PowerShellISEHelp

So it would appear that the error message in Windows Server 2008 at the top of this post is slightly misleading………

Extending the System Disk in Windows Server 2008

As a system administrator it is often not the latest and greatest big new features of a new operating system which you end up finding the most useful, sometimes its the small improvements which really make your life easier. I’ve been spending a lot more time with Windows Server 2008 recently (OK, I know R2 is out, but it takes time for large organisations to move away from older OS’s and applications.)

Obviously there are great new headline features with things like Server Core and Read-Only Domain Controllers, but I have found a new feature I love which is the abilty to extend a system disk without having to go through some kind of convoluted process. It is particularly easy if it is a VM and you have enough space on the existing datastore to extend the VM’s disk without requesting more storage from your SAN team.

In VSphere 4.0 it even lets me extend the size of the vmdk with the server powered on, I don’t remember that being possible in 3.5, but I might be wrong. The big advantage for this for me is the fact that you do not need downtime to carry out this whole process, either extending the vmdk or the system volume!

Update: I just read in Mastering vSphere by Scott Lowe that the above is a new feature in vSphere 4.0 and the VM is required to be hardware version 7.

Take the following steps to carry it out:

1) Edit the settings of the 2008 VM and increase the size of the disk.

2) Within the Disk Management MMC, kick off a ‘Rescan Disks’

Rescan

3) You will now see the additional space available at the end of the current disk.

Extra2GB

4) Right-click the C: drive and choose ‘Extend Volume’

Extend Volume

5) Follow the wizard through to add the additonal space. On completion your system disk will now have the additional space added – all with no downtime!

17GB

For completeness there are a number of ways you could do this in Window Server 2003, the way I have typically used is the following. (Make sure you have a good backup of this VM before going ahead)

1) Power down the VM. Edit the settings of the VM to grow the VMDK. Do not power on the VM!

2) Find another Windows Server 2003 VM which is Powered On and has access to the same datastores. Edit the settings of the VM, add an additional hard disk, select the option to use an existing virtual disk, browse to the initial VM’s vmdk and select it.

3) In Disk Management Rescan the disks and you will see an additional drive with unallocated space available other than your current drives.

4) Use the command line utility diskpart to extend this drive.

  • Enter diskpart to start
  • List Volume will show available volumes on the machine
  • Check the additional volume has not been listed as a System partition
  • Select that volume with Select Volume volumenumber
  • Enter Extend to increase the size of the drive with all available space
  • List Volume will let you check it was successful
  • Exit to finish

Go back into Disk Management and you should be able to see a larger drive.

5) Edit the settings of the VM and remove the additional disk – make sure you do not select the option to delete the files from disk!

6) Power the initial VM back on. After logging back in the first time you will most likely be prompted to restart the server. Once logged back in, check Disk Management to see the larger system disk.