Tag Archives: Scripting

Which Disk is that volume on?

I administer a server VM with a lot of disks, and many of them are the same size. When I need to make changes to the system’s storage, I’m always nervous that I’m going to poke the wrong disk. I could trust that the order of the disks listed in the vSphere client is the same as the order that the guest OS lists (starting at 1 and 0 respectively). But I want a little more assurance.

Using diskpart, you can list the details for individual disks, partitions and volumes, but I wanted a report showing all the disks, the partitions on those disks, and the volumes residing on those partitions. I have reported some of this info previously, using PowerShell’s Get-WMIObject cmdlet to query the Win32_DiskDrive, Win32_Partition, and Win32_Volume classes. I figured there must me a way to correlate instances of these classes.

I found these two blog posts:

They did most of the heavy lifting in building the WQL ASSOCIATOR OF queries. I put together a short script to give me a little more detail. Here’s some sample output:

PS C:localscripts> .Get-DiskInfo.ps1
Disk 0 - SCSI 0:0:2:0 - 45.00 GB
    Partition 0  100.00 MB  Installable File System
    Partition 1  44.90 GB  Installable File System
        C: [NTFS] 44.90 GB ( 3.46 GB free )
[...]
Disk 5 - SCSI 0:0:2:5 - 39.99 GB
    Partition 0  40.00 GB  Installable File System
        B: [NTFS] 40.00 GB ( 34.54 GB free )

This will make it easier to be sure about the vSphere storage element that corresponds to a particular volume (or, more accurately, the Physical Disk on which the volume resides).

Here’s the actual script:

 .Get-DiskInfo.ps1

.NOTES
    Author: Geoff Duke 
    Based on http://bit.ly/XowLns and http://bit.ly/XeIqFh
#>

Set-PSDebug -Strict

Function Main {

    $diskdrives = get-wmiobject Win32_DiskDrive | sort Index

    $colSize = @{Name='Size';Expression={Get-HRSize $_.Size}}

    foreach ( $disk in $diskdrives ) {

        $scsi_details = 'SCSI ' + $disk.SCSIBus         + ':' +
                                  $disk.SCSILogicalUnit + ':' +
                                  $disk.SCSIPort        + ':' +
                                  $disk.SCSITargetID
        write $( 'Disk ' + $disk.Index + ' - ' + $scsi_details +
                 ' - ' + ( Get-HRSize $disk.size) )

        $part_query = 'ASSOCIATORS OF {Win32_DiskDrive.DeviceID="' +
                      $disk.DeviceID.replace('','\') +
                      '"} WHERE AssocClass=Win32_DiskDriveToDiskPartition'

        $partitions = @( get-wmiobject -query $part_query | 
                         sort StartingOffset )
        foreach ($partition in $partitions) {

            $vol_query = 'ASSOCIATORS OF {Win32_DiskPartition.DeviceID="' +
                         $partition.DeviceID +
                         '"} WHERE AssocClass=Win32_LogicalDiskToPartition'
            $volumes   = @(get-wmiobject -query $vol_query)

            write $( '    Partition ' + $partition.Index + '  ' +
                     ( Get-HRSize $partition.Size) + '  ' +
                     $partition.Type
                   )

            foreach ( $volume in $volumes) {
                write $( '        ' + $volume.name + 
                         ' [' + $volume.FileSystem + '] ' + 
                         ( Get-HRSize $volume.Size ) + ' ( ' +
                         ( Get-HRSize $volume.FreeSpace ) + ' free )'
                       )

            } # end foreach vol

        } # end foreach part

        write ''

    } # end foreach disk

}

#--------------------------------------------------------------------
function Get-HRSize {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
        [INT64] $bytes
    )
    process {
        if     ( $bytes -gt 1pb ) { "{0:N2} PB" -f ($bytes / 1pb) }
        elseif ( $bytes -gt 1tb ) { "{0:N2} TB" -f ($bytes / 1tb) }
        elseif ( $bytes -gt 1gb ) { "{0:N2} GB" -f ($bytes / 1gb) }
        elseif ( $bytes -gt 1mb ) { "{0:N2} MB" -f ($bytes / 1mb) }
        elseif ( $bytes -gt 1kb ) { "{0:N2} KB" -f ($bytes / 1kb) }
        else   { "{0:N} Bytes" -f $bytes }
    }
} # End Function:Get-HRSize

Main

Please let me know if you find this helpful.

String arrays and mandatory parameters

I have been working on a function to convert the output of NET SHARE commands into usable PowerShell objects. In the course of my work, I was storing the output of the command in a variable, which I later pass into a parsing function. Curiously, the function I developed iteratively in the console worked fine, but when I dressed it up in my script, it failed:

test-array : Cannot bind argument to parameter 'foo' because it is an empty string.
At line:1 char:12
+ test-array $party
+            ~~~~~~
    + CategoryInfo          : InvalidData: (:) [test-array], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-array

I had verified that the variable was of type System.Array, and that it had string elements. After banging my head on it for a while, I decided to break out the parameter handling and test it separately. I wrote a quick function to accept and process the elements of a string array:

function test-array {
param( [string[]] $foo )
    $i = 0
    foreach ( $line in $foo ) {
        write "[$i] $line"
        $i++
    }
}

Continue reading

Listing parent of AD object in PowerShell

Recently, I wanted to provide a client with a list of groups that related to some work he was doing. I wanted the group names as well as their location with AD. Although I often use the ds* commands or excellent ADfind tool for this type of task, I had been working in PowerShell on another project, so I decided to use the PowerShell ActiveDirectory module.

The Get-ADGroup Cmdlet pulled out the groups easily enough, but the there wasn’t a property representing the group object’s parent, nor is there an LDAP property that I could request (AFAIK). The object’s parent is contained within the DistinguishedName (DN) property, though.

For a group with the following DN:

CN=FOO-FileServices Administrators,OU=FOO,OU=Departments,DC=uvm,...

I just need to strip off the CN. I could split the DN on commas, remove the first element, and then reassemble what’s left to get the parent. I also needed to avoid splitting on an LDAP-escaped comma where a value actually contains a comma (e.g., CN=).

PS> $dn -split '(?<![\]),'

Continue reading

Script: Shadow Copy Report

We use EMC NetWorker for our enterprise backup solution. Since we migrated our primary file server from a NetApp filer to a native Windows server, we’ve been having a recurring problem with all the Shadow Copies for a volume getting deleted. There are strong indications that the problem is related to the NetWorker backups.

As we have been working on this issue with EMC (since the first week in January!), I wrote a script to tell me two things each morning; how many snapshots exist for each volume, and what VSS errors were logged, if any.

I thought someone might find it useful, so I’ve posted it as a separate page (the script doesn’t fit nicely in the column on the blog).

PowerShell Script: chksnap.ps1

Custom FSRM notification script

I’ve been working on a script to generate an informative message to users when they exceed quota thresholds on our file server. The features of the File Server Resource Manager (FSRM) provides a variety of useful variables that can be plugged into an automated email. However, we have found that it’s often very useful to provide more information about the kind of files that a user is storing, something akin to the output of the very useful and free utility WinDirStat.

I’ve made progress on the script that generates the email. However, I’ve run into a snarl in trying to configure the quota notification to run the script. The script runs just fine from a command prompt, even from a command prompt running as the Local System account. But when I trigger an FSRM event that should drive the script, I get an error in the Application Log:

 

Continue reading

PowerShell – find a free IP

Since we don’t use DHCP in our server subnets, I frequently have to locate free IP addresses when deploying a server. I remembered reading a TechNet Magazine article by Don Jones that used the PowerShell PROCESS block and the Win32_PingStatus WMI class in a sample script.

I took that and rewrote the function a little:

function Ping-Address {
  PROCESS {
    $ping = 'unreachable'
    $formatstring = "{0,-15}  {1,-12} {2}"
    $queryString  = "SELECT * FROM Win32_PingStatus"
    $queryString += " WHERE Address = '$_' AND"
    $queryString += " ResolveAddressNames = $true"
    $results = Get-WmiObject -query $queryString
    
    foreach ($result in $results) {
      if ($results.StatusCode -eq 0) {
        $ping = 'ping!'
      }
    }
    $formatstring -f $_,$ping,$results.ProtocolAddressResolved
   }
}

I can then use this function like so:

PS Z:> (14..20) | %{ '132.198.59.'+ $_.ToString()} | Ping-Address
132.198.59.14    ping!        132.198.59.14
132.198.59.15    ping!        132.198.59.15
132.198.59.16    ping!        xxxxxxx.campus.ad.uvm.edu
132.198.59.17    ping!        xxxxxxx.uvm.edu
132.198.59.18    unreachable
132.198.59.19    ping!        xxxx.uvm.edu
132.198.59.20    unreachable

I’ve already used it a bunch of times. I think I will probably grow this into a real script, taking the IP address range info as parameters. Another day…

Monday – 2009-09-28

Today’s issues:

  • Backup issues
  • Shared folder quotas
  • Printer configurations
  • Data execution protection

I created a Server 2008 x64 guest for managing 64-bit drivers on our shared printers. It works much better than trying to use Printer Management MMC in RSAT on Windows 7.

One hiccup I ran into while install the Ricoh PCL6 Driver for Universal Print was that it was missing a file. Fortunately, I had also download and extracted the non-universal PCL6 drivers and the file was present in the drivers for the corresponding platforms (x86, x64).

Looking at adding a –WhatIf switch parameter to my SharePoint Backup powershell script. Useful info at Negating PowerShell switch parameters.

Now wrestling with Task Scheduler and PowerShell invocation syntax.

backups – Bad and Good

We’ve been working with our backup vendor to address some shortcomings of their product as it relates to Windows 2008 system recover. This was precipitated by a failure of a portion of our virtual infrastructure, which lead to corruption of several hosts’ virtual disk files.

We managed to rebuild one failed host from bare (virtual) metal, because EMC Networker could not recover the system from backups. For Server 2008 systems, they require backups made with client 7.5.1 and restored with 7.5.1 and you have to enable/install any server role that was present on the original system before performing the restore.

We’ve been working on other ways to make sure we can recover from a system failure. Greg has successfully scripted using server 2008’s printer management scripts to dump printer info to files. I’ve been working on scripted backup of SharePoint Site collections. I got some help from Microsoft in determining the correct permissions needed for a service account to perform STSADM backup operations, which has been a thorny issue. ( see KB896148 )

Tuesday – May 5

Spurred by some recent traffic on the Windows-HiEd list, I have looked into the Windows Update process on some of our Server 2008 Core systems. The thread was specifically with regard to KB article 953631, and that some folks have found that it installs repeatedly on Server Core instances and blocks other updates.

In examining the event logs on a couple of our Server Core system, I found that the update is indeed re-installing repeatedly, but it doesn’t appear to be blocking other updates.

First, I ran the systeminfo command to display the installed updates. KB953631 was not listed. I grabbed the WUA_SearchDownloadInstall.vbs script from Microsoft (I renamed it to Get-WindowsUpdates.vbs, in keeping with the sound PowerShell naming conventions). When I ran the script, it found and downloaded two updates, the KB953631 update in question, and KB955430. I confirmed that I wanted the updates installed, and the first update installed successfully, but the second failed (my initial searches didn’t explain the 0x800f082f error code). I reproduced the same behavior on another server core instance.

I tried rebooting the host, and running the Get-WindowsUpdates.vbs script again, and this time both updates installed successfully. (yes, the KB953631 update installed again). I reproduced this success on the other host as well.

So it appears that in our environment, the KB953631 update isn’t blocking other updates. I’ll confirm this after Patch Tuesday.

At the very end of the KB article is the following:

Note for WSUS administrators
If you approve this update for deployment in a WSUS environment, be aware that after you run the update, it will not be reported as "Installed." The update itself is not installed on client computers. The update scans for missing files and replaces them as appropriate. If a computer requires a missing file, the 953631 update will be reported as "Needed.”

Also, Server Core is not mentioned specifically in the list of affected operating systems. It might be worth asking what the expected correct behavior should be in this situation.

In my investigating, I also found an article in the Scripting Center the describes a PowerShell approach to manipulating Windows Updates. This might be nice when Server 2008 R2 is availabel and .NET and PowerShell are included, or other update-wrangling tasks.

Monday – March 23

Goal for today: finish moving provisioning.

Made some initial progress on separating functionality into different modules. Lots of reading about modules, semantics of use and require. I should probably be creating tests at the same time. Will this ever get done?!