WMI

Getting Automatic Services That Are Stopped With PowerShell

One of the first things I check when I am troubleshooting a system is whether all the services that should be running, are. I could just open up services.msc, click on the "Startup Type" column to sort by the startup type, and scroll down through the Automatic services to see which ones aren't running. But that's so…. GUI  :p. And slower, and so very one machine at a time. Instead, let's use PowerShell to make it all easier.

 

First, I checked Get-Service, thinking it would give me what I need. but it doesn't. There's no way with Get-Service to find out what the startup type is -- it's not a property returned by Get-Service. (Yes, I think this is a deficiency. And yes, I expect someday we might get an improvement to Get-Service. But for the moment, we have to work around it. )

 

Instead, I decided to use the Get-WmiObject cmdlet to find what we need. (If the machine you're running this from is running PowerShell v3 or later, you can substitute Get-CimInstance for Get-WmiObject. But if you do, you won't be able to use -Credential.)

 

Get-WmiObject Win32_Service returns a list of all the services on the local machine. We can extend it with -ComputerName to query the services on a remote computer. And we can filter those services, though the filtering uses WQL as the query language, which is a nuisance since it doesn't match up to the Filter syntax for the ActiveDirectory module, for example.

 

To get a list of all the services that should have started automatically, but that are not currently running, on the local machine:

Get-WmiObject -ClassName Win32_Service -Filter "StartMode='Auto' AND State<>'Running'"

But that output is a bit ugly, so we'll throw some Format-Table at it, and come up with:

Get-WmiObject -ClassName Win32_Service `
              -Filter "StartMode='Auto' AND State<>'Running'" `
             | Format-Table -Auto DisplayName,Name,StartMode,State

Not bad. That gives us an easy to read output with all the information we need. We can wrap that up in a simple cmdlet that assumes the local computer, but that allows us to run it against multiple computers. And we want it to be able to get that list of computer names through the pipeline, of course. Plus, we'll add a Credential parameter to allow us to run against machines on a different domain, or a workgroup, so long as we provide an appropriate credential.

 

If we're going to get output from multiple computers, however, we need to know which one has which services that aren't running. To do that, we take advantage of Format-Tables GroupBy parameter:

Get-WmiObject -ClassName Win32_Service `
              -Filter "StartMode='Auto' AND State<>'Running'" `
             | Format-Table -AutoSize `
                            -Property DisplayName,Name,StartMode,State `
                            -GroupBy  PSComputer

Now we have everything we need to pull our script together.

Get-myStoppedService.ps1

<#
.Synopsis
Gets a list of stopped services
.Description
Get-myStoppedService takes a list of computer names and returns 
a table of the stopped services on that computer that are set to 
automatically start. The default is to return a list on the local computer.
.Example
Get-myStoppedService
Returns a table of stopped services on the local computer
.Example
Get-myStoppedService -ComputerName 'server1','client2'
Returns a table of stopped services on server1 and client2, 
grouped by computer name
.Parameter ComputerName
A list of remote computer names to query. If the current account 
doesn't have permission to query WMI on the remote computer, use 
the Credential parameter to provide alternate credentials. 
The default is the local host.
.Parameter Credential
Standard PSCredential object. Use Get-Credential.
.Inputs
[string[]]
[PSCredential]
.Notes
    Author: Charlie Russel
 Copyright: 2016 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 29 September, 2016 (cpr)
   ModHist:
          :
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$False,Position=0,ValueFromPipeline=$True)]
     [Alias("Name","VMName")]
     [string[]]
     $ComputerName = ".",
     [Parameter(Mandatory=$False,ValueFromPipeline=$True)]
     [PSCredential]
     $Credential = $NULL
     )

if ($Credential) {
   Get-WMIObject -ClassName Win32_Service `
                 -Credential $Credential `
                 -ComputerName $ComputerName `
                 -Filter "StartMode='Auto' AND State<>'Running'" `
                | Format-Table -Auto DisplayName,Name,StartMode,State -GroupBy PSComputerName
} else {
   Get-WmiObject -ClassName Win32_Service `
                 -ComputerName $ComputerName `
                 -Filter "StartMode='Auto' AND State<>'Running'" `
                | Format-Table -Auto DisplayName,Name,StartMode,State -GroupBy PSComputerName
}

Setting Console Colours

As I described in my previous post, I always open both an admin and non-admin PowerShell window when I log on to a computer. To tell the two apart, I set the background colour of the Admin window to dark red, with white text, and the non-admin window to a white background with dark blue text. The result is clearly and immediately different, warning me when I'm running as an administrator. To do that, I use the following:

$id = [System.Security.Principal.WindowsIdentity]::GetCurrent() 
$p = New-Object system.security.principal.windowsprincipal($id)

# Find out if we're running as admin (IsInRole). 
# If we are, set $Admin = $True. 
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)){ 
   $Admin = $True 
} else { 
   $Admin = $False 
}

if ($Admin) { 
      $effectivename = "Administrator" 
      $host.UI.RawUI.Backgroundcolor="DarkRed" 
      $host.UI.RawUI.Foregroundcolor="White" 
      clear-host 
   } else { 
      $effectivename = $id.name 
      $host.UI.RawUI.Backgroundcolor="White" 
      $host.UI.RawUI.Foregroundcolor="DarkBlue" 
      clear-host 
}

 

That works great for older versions of the Windows PowerShell console, but starting with PowerShell v5, that can have an unintended side effect. In PowerShell v5, we now have PSReadLine that does context sensitive colouration of the command line. But the default PowerShell console has a dark blue background, with white text. And when I changed the background colour of my non-admin PowerShell window to white, it gets a little hard to read!! So, to fix that, I use Set-PSReadLineOption to change the various kinds of context sensitive colour changes to something that works with a light background. We don't want to do that for the dark red background of the Admin window, so we'll need to check which colour we are and adjust accordingly.

First, get the current colour:

$pData = (Get-Host).PrivateData 
$curForeground = [console]::ForegroundColor 
$curBackground = [console]::BackgroundColor

You'll only want to configure the context sensitive colouring options if you're running on Windows 10 or Server 2016. Prior versions of Windows didn't have the new system console that comes with Windows 10. So you'll want to check that the build number is > 10240

$Build = (Get-WmiObject Win32_OperatingSystem).BuildNumber

If $Build -ge 10240, then set the various context sensitive tokens to work with the colour we have.

# PowerShell v5 uses PSReadLineOptions to do syntax highlighting. 
# Base the color scheme on the background color 
If ( $curBackground -eq "White" ) { 
      Set-PSReadLineOption -TokenKind None      -ForegroundColor DarkBlue  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Comment   -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Keyword   -ForegroundColor DarkGreen -BackgroundColor White 
      Set-PSReadLineOption -TokenKind String    -ForegroundColor Blue      -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Operator  -ForegroundColor Black     -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Variable  -ForegroundColor DarkCyan  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Command   -ForegroundColor DarkRed   -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Parameter -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Type      -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Number    -ForegroundColor Red       -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Member    -ForegroundColor DarkBlue  -BackgroundColor White 
      $pData.ErrorForegroundColor   = "Red" 
      $pData.ErrorBackgroundColor   = "Gray" 
      $pData.WarningForegroundColor = "DarkMagenta" 
      $pData.WarningBackgroundColor = "White" 
      $pData.VerboseForegroundColor = "DarkYellow" 
      $pData.VerboseBackgroundColor = "DarkCyan" 
   } elseif ($curBackground -eq "DarkRed") { 
      Set-PSReadLineOption -TokenKind None      -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Comment   -ForegroundColor Gray     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Keyword   -ForegroundColor Yellow   -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind String    -ForegroundColor Cyan     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Operator  -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Variable  -ForegroundColor Green    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Command   -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Parameter -ForegroundColor Gray     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Type      -ForegroundColor Magenta  -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Number    -ForegroundColor Yellow   -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Member    -ForegroundColor White    -BackgroundColor DarkRed 
      $pData.ErrorForegroundColor   = "Yellow" 
      $pData.ErrorBackgroundColor   = "DarkRed" 
      $pData.WarningForegroundColor = "Magenta" 
      $pData.WarningBackgroundColor = "DarkRed" 
      $pData.VerboseForegroundColor = "Cyan" 
      $pData.VerboseBackgroundColor = "DarkRed" 
   } 
}

Finally, let's make sure that console window is the right size, and while we're at it, set the window title. (This is a workaround for a PITA bug in recent builds of Windows 10/Server 2016 that seems to have problems setting the console window size and keeping it!)

$host.ui.rawui.WindowTitle = $effectivename + "@" + $HostName +" >" 
$Host.UI.RawUI.WindowSize = New-Object System.Management.Automation.Host.Size(120,40)

 

Now, with all of this, you have effective, context-sensitive, command-line colouring of your PowerShell windows.

Unmapping Network Drives

Unmapping network drives with PowerShell should be easy, and it is, but with some caveats. If you always create your network drive mappings with New-PSDrive, then it's easy to unmap them with Remove-PSDrive. But if some of them are created with Group Policy, some of them with the legacy "net use" commands, some of them with New-SmbMapping, and some with New-PSDrive, it's really not that easy to fully clean up the list of mapped drives. I've used a variety of techniques over the years to map and unmap drives, and I've finally come to the conclusion that using New-PSDrive and Remove-PSDrive is the cleanest way for most things. However, if you've got a mix of mapped drives, created with more than one method, here's a script to remove them all.

<#
.SYNOPSIS
Unmaps network drives
.DESCRIPTION
Unmapdrives removes all currently mapped network drives. It's smart enough to 
remove drives mapped with "net use", "New-SmbMapping" and "New-PSDrive". This 
cmdlet accepts no parameters and assumes -Force for all unmappings. 

.EXAMPLE
UnMapDrives 
Unmaps all currently mapped network drives 

.NOTES
    Author: Charlie Russel
 Copyright: 2015 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 06/27/2015 (cpr)
   ModHist:
 :
#>
[CmdletBinding()]

# Build a dynamic list of currently mapped drives
$DriveList = Get-WMIObject Win32_LogicalDisk `
     | Where-Object { $_.DriveType -eq 4 }

# Don't bother running this if we don't have any mapped drives
 if ($DriveList) { 
    $SmbDriveList = $DriveList.DeviceID
 } else {
    Write-Host "No mapped drives found"
    Return
}

Write-host "Unmapping drive: " -NoNewLine
Write-Host $SmbDriveList
Write-Host " "

Foreach ($drive in $SmbDriveList) {
    $psDrive = $drive -replace ":" #remove unwanted colon from PSDrive name
    Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile
    If ( (Get-PSDrive -Name $psDrive) 2>$Null ) {
       Remove-PSDrive -Name $psDrive -Force
    }
}
Write-Host " "

# Report back all FileSystem drives to confirm that only local drives are present. 
Get-PSDrive -PSProvider FileSystem

This is the simple form of a more generalized script that can accept a parameter of either -All or a list of mapped drive letters to remove. (And yes, unmap isn't an approved verb. But this script started out life many, many years ago as a batch file ("unmapdrives.cmd"), so it's still got the same base name it has always had, because that's what my fingers remember!)

Getting the Free Disk Space of Remote Computers

This started out as a simple script to try to get the free space on one of my servers, but I quickly discovered that using WMI’s Win32_LogicalDisk could only give me part of the solution. The catch is that Win32_LogicalDisk doesn’t return the information about volumes that aren’t assigned drive letters. Which is a problem if what you really need to know is how close to out of space your backup disk is! Because Windows Server Backup takes total control of the backup target disk, and doesn’t mount it as a drive letter, you need to use a different WMI Class to get the information you need. After asking some friends (PowerShell MVPs ROCK!), I was pointed to Win32_Volume, which returns all sorts of information about disk volumes whether they are assigned drive letters or not.

The next issue was how to get the actual information I wanted, and then to format it so that it actually made some sense. For example:

PSH> (Get-WmiObject –ComputerName Server1 –Class Win32_Volume).FreeSpace
21654667264
103541030912
75879378944
142417367040
5500928
565375053824
PSH>

This doesn’t really cut it. OK, let’s try at least getting it into a table:

PSH> Get-WmiObject –ComputerName Server1 –Class Win32_Volume | ft –auto DriveLetter,Label,FreeSpace
DriveLetter Label                               FreeSpace 
----------- -----                               --------- 
C:                                            21655351296 
D:          DATA                             103541030912 
E:          EXCHANGE                          75879378944 
F:          FILES                            142417367040 
Y:          New Volume                            5500928 
            Server1 2014_10_15 10:57 DISK_03 565375053824

Well, that’s a bit more useful, but frankly, that number for the backup volume seems big, but is it 500 GB, or 50 GB? At first glance, I have no idea. And if it’s 50 GB, I’m in trouble, but if it’s 500 GB, we’re fine. So, we need to do a bit of manipulation to the output from Format-Table, and the tool for this is to create an Expression that allows you to calculate and format a result in a way that makes more sense. For this, we use an expression as the property to display. So, for example, to display that “565375053824” as Gigabytes, we use:

PSH>
Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  Label,`
                  @{Label=”Free(GB)”;Expression={'{0:N0}’ –F ($_.FreeSpace/1GB)}}
DriveLetter Label                            Free(GB) 
----------- -----                            -------- 
C:                                           20 
D:          DATA                             96 
E:          EXCHANGE                         71 
F:          FILES                            133 
Y:          New Volume                       0 
            Server1 2014_10_15 10:57 DISK_03 527 
PSH>

Now we’re getting somewhere. But what did we do? We use the @{} to tell Format-Table that we were going to use an expression to define a column of data. The Label=”Free(GB)” creates a new column header, and the Expression={“{0:N0}” –F  means we’re going to have a numeric value (including thousands separators) with no decimal values. The calculated value for the column is ($_.FreeSpace/1GB).

So we now have a useful listing of free space on the remote server. Of course, it might be even more useful to know the percentage free. No problem, for that we use the formatting expression “{0:P0}” to express the column as a percentage, and use the calculation of ($_.FreeSpace/$_.Capacity), letting PowerShell do the work of converting that to a percentage. So:

PSH>
Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  Label,`
                  @{Label=”Free(GB)”;Expression={“{0:N0}” –F ($_.FreeSpace/1GB)}},`
                  @{Label=”%Free”;Expression={“{0:P0}” –F ($_.FreeSpace/$_.Capacity)}}
DriveLetter Label                         Free(GB)  %Free 
----------- -----                         --------  ----- 
C:                                            20      17 % 
D:          DATA                              96      48 % 
E:          EXCHANGE                          71      71 % 
F:          FILES                             133     18 % 
Y:          New Volume                        0       58 % 
            Server1 12014_10_15 10:57 DISK_03 527     51 % 
PSH>

Now we almost have it. Next, it would probably be useful to get the total capacity of the disk while we’re at it, and since I have more than one server, we should probably plan on passing this whole thing an array of computer names. So, the final script, at least for this first pass:

# ********************************************* 
# ScriptName: Get-myFreeSpace.ps1 
# 
# Description: Script to get the free disk space
#            : on a remote computer and display it usefully
# 
# ModHist: 26/11/2014 - Initial, Charlie
#        : 
# 
# 
# *********************************************
[CmdletBinding()]
Param ([Parameter(Mandatory=$False,Position=0)]
         [String[]]$ComputerName = "Server1")
Write-Host ""
ForEach ( $Name in $ComputerName ) {
   Write-Host "Disk Utilization for server $Name is: "
   Get-WmiObject  -ComputerName $Name -Class Win32_Volume `
      | Format-Table  -auto `
         @{Label="Drive";`
            Expression={$_.DriveLetter};`
            Align="Right"},`
         @{Label="Free(GB)";`
            Expression={"{0:N0}" -f ($_.FreeSpace/1GB)};`
            Align="Right"},`
         @{Label="% Free";`
            Expression={"{0:P0}" -f ($_.FreeSpace / $_.Capacity)};`
            Align="Right"},`
         @{Label="Size(GB)";`
            Expression={"{0:N0}" -f ($_.Capacity / 1GB)};`
            Align="Right"},`
         @{Label="Volume Label";`
            Expression={$_.Label};`
            Width=25} 
}

You’ll see I tweaked the formatting to right align the calculated expressions, and gave my volume label column some extra space to improve readability. The result is:

PSH> Get-myFreeSpace.ps1 –ComputerName “Server1”,”Server2”
Disk Utilization for server Server1 is:
 Drive Free(GB) % Free Size(GB) Volume Label 
----- -------- ------ -------- ------------ 
   C:       20   17 %      120 
   D:       96   48 %      200 DATA 
   E:       71   71 %      100 EXCHANGE 
   F:      133   18 %      750 FILES 
   Y:        0   58 %        0 New Volume 
           527   51 %    1,024 Server1 2014_10_15 10:57 DISK_03 
   
Disk Utilization for server Server2 is:
Drive Free(GB) % Free Size(GB) Volume Label 
----- -------- ------ -------- ------------ 
             0   25 %        0 
   D:    1,697   53 %    3,214 Data 
   C:      484   95 %      512 
PSH>

Finding out what RAM DIMMs are installed on a computer without opening the box

OK, so I needed to know exactly what RAM was installed on a computer and how many slots there were. I could have had the user shutdown his machine, opened up the box, got out a flashlight, popped out the DIMMs, written down the obscure part numbers, plugged them back in, and closed it up. But not only was that way too much like work, it would totally have disrupted his workday. So, instead, I let PowerShell and WMI do the heavy lisfting. The following script will get the currently installed memory modules, the maximum RAM supported on the computer, and the number slots available.

#****************************************
#
# Script Name: Get-MemoryModule.ps1
#
# Script to get the number of memory slots, and the installed memory, on
# the local computer. Could easily be updated to go against remote computers.
#
# ModHist: 12/29/2012 - initial, Charlie.
#        : 08/26/2014 - Charlie. Added logic to get slots and max RAM.
#        :
#****************************************
$strComputer = "."
$ComputerName = (hostname)
$colSlots = Get-WmiObject `
               -class "Win32_PhysicalMemoryArray" `
               -namespace "root\CIMV2" `
               -Computername $strComputer
$nSlots = $colSlots.MemoryDevices
$nMax = $colSlots.MaxCapacity
$gbMax = $nMax/(1024*1024)
$colModules = Get-WMIObject `
                -class "Win32_PhysicalMemory" `
                -namespace "root\CIMV2" `
                -computername $strComputer
foreach ($objItem in $colModules) {
   Write-host "Bank Label: " $objItem.BankLabel
   write-host "Capacity: " $objItem.Capacity
   write-host "Caption: " $objItem.Caption
   write-host "Creation Class Name: " $objItem.CreationClassName
   write-host "Data Width: " $objItem.DataWidth
   write-host "Description: " $objItem.Description
   write-host "Device Locator: " $objItem.DeviceLocator
   write-host "Form Factor: " $objItem.FormFactor
   write-host "Hot-Swappable: " $objItem.HotSwappable
   write-host "Installation Date: " $objItem.InstallDate
   write-host "Interleave Data Depth: " $objItem.InterleaveDataDepth
   write-host "Interleave Position: " $objItem.InterleavePosition
   write-host "Manufacturer: " $objItem.Manufacturer
   write-host "Memory Type: " $objItem.MemoryType
   write-host "Model: " $objItem.Model
   write-host "Name: " $objItem.Name
   write-host "Other Identifying Information: " $objItem.OtherIdentifyingInfo
   write-host "Part Number: " $objItem.PartNumber
   write-host "Position In Row: " $objItem.PositionInRow
   write-host "Powered-On: " $objItem.PoweredOn
   write-host "Removable: " $objItem.Removable
   write-host "Replaceable: " $objItem.Replaceable
   write-host "Serial Number: " $objItem.SerialNumber
   write-host "SKU: " $objItem.SKU
   write-host "Speed: " $objItem.Speed
   write-host "Status: " $objItem.Status
   write-host "Tag: " $objItem.Tag
   write-host "Total Width: " $objItem.TotalWidth
   write-host "Type Detail: " $objItem.TypeDetail
   write-host "Version: " $objItem.Version
   write-host
}

Write-Host -nonewline "Computer $ComputerName has $nSlots Memory Slots, and a Maximum" 
write-host "Memory of $gbMax GigaBytes of RAM" $colModules `
     | ft -auto "BankLabel",@{Label="CurrentMem(MB)";Expression={$_.Capacity/(1024*1024)}},"Speed"

I’ve shown the “local” version of this, but it would be trivial to modify the script to run against a remote computer by adding support for a –ComputerName parameter. Also, at least some computers (most notably laptops) will not report their maximum supported memory correctly. I’ve run this script against the 6 physical computers running here and all worked fine expect for my HP laptop. That has 16 GB of RAM in it, but says the maximum RAM is 8 GB.

Getting Drive Information Using WMI and PowerShell

In my ongoing quest to remove dependency on legacy DOS commands, I recently created this script to get a list of all the local and mapped drives on a machine and how much free space each has on it.  This script could be easily extended to display additional information (such as the underlying filesystem type) by simply adding to the output tables. All the information is already captured in the Get-WMIObject call at the beginning, but the fun is in the formatting (and math)  of the tables to provide useful information at a glance.

 # Filename: GetDrives.ps1
 #
 # A script to get a list and information on the "local" drives without using "Net"
 #
 # ModHist: 08/05/2013 - Initial
 #                   :
 #
 # ***********************************************************************
 $LogicalDisks = Get-WMIObject Win32_LogicalDisk
 $LocalHDisks = $LogicalDisks | Where-Object { $_.DriveType -eq 3 }
 $RemoteDrives = Get-WMIObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 }
 $OpticalDrives = Get-WMIObject Win32_CDRomDrive
$LocalHDisks | ft -auto @{Label="Drive";`
                           Expression={$_.DeviceID};
                           width=5
                           align="Right"},`
                        @{Label="Volume Label";`
                           Expression={$_.VolumeName};                                                         
                           Width=25},` 
                        @{Label="%Free";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`
                           Width=8},`
                        @{Label="GBFree";`
                           Expression={$([math]::round(($_.FreeSpace/1gb),0))};`
                           Width=8},`
                        @{Label="Size(GB)";`
                           Expression={$([math]::round(($_.Size/1gb),0))};`
                           Width=8}
 $OpticalDrives | Sort Drive | ft -auto `
                        @{Label="Drive";`
                           Expression={$_.Drive};`
                           Width=5
                           Align="Right"},
                        @{Label="Capabilities";`
                           Expression={ if ($_.Capabilities -eq 4) { `
                             "Read/Write" `
                           } else { `
                             "Read-Only"}}},`
                        @{Label="Disk Volume Label";`
                           Expression={$_.VolumeName}}
 $RemoteDrives | ft -auto @{Label="Drive";`
                           Expression={$_.DeviceID};`
                           width=5
                           align="Right"},`
                        @{Label="Remote Share";`
                           Expression={$_.ProviderName};`
                           Width=25},
                        @{Label="%Free";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`
                           Width=8},`
                        @{Label="GBFree";`
                           Expression={$([math]::round(($_.FreeSpace/1gb),0))};`
                           Width=8},`
                        @{Label="Size(GB)";`
                           Expression={$([math]::round(($_.Size/1gb),0))};`
                           Width=8}

 

If you find this script interesting or useful, great. And if you have improvements to it, I'd be delighted to hear about them.

Charlie.

PowerShell v3 – Using PSDrive to Replace Net Use

I routinely have to map drives across domain boundaries, or to/from non-domain and domain machines. In the old days, I used NET USE commands, which were OK, but there were some issues. Besides, it’s time to move to away from legacy commands such as NET. PowerShell v3 includes an updated set of PSDrive cmdlets (Get, New, Remove) that have added the ability to create persistent mappings to a drive letter. Plus, unlike NET USE commands, I can pass a single credential to connect to multiple machines, and prompt for the password. Ah, HA. Now that’s useful. Here’s my drive mapping script for connecting to three different machines with my domain credentials, even though I’m actually connecting from a non-domain joined machine.

# PowerShell script to map drives using New-PSDrive command. 
# Prompts once for credentials, then uses them. Or so we hope. 
# 
# Initial: 10 June, 2012 
#

# Start by checking for already mapped drives. We’ll use Get-WMIObject to query Win32_LogicalDisk.
# A drivetype of 4 means that the drive is a network drive.

$NetDrives = Get-WMIObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 }

# Check which servers have drives mapped to them.
$Srv1Mapped = $NetDrives | Where-Object {$_.ProviderName -match "srv1" } 
$wssMapped = $NetDrives | Where-Object { $_.ProviderName -match "wss-100" }

# Prompt for credentials and store in a variable. 
$Contoso = Get-Credential -Cred "CONTOSO\Charlie" 

# Now, map drives based on that credential 
# First, drives on SRV1. These are general Contoso resources 
if ($Srv1Mapped ) { 
   Echo "Skipping core maps on SRV1" 
} else { 
   New-PSDrive -Name I –root \\srv1\install    -scope Global -PSProv FileSystem -Cred $Contoso –Persist 
   New-PSDrive -Name J -root \\srv1\Download   -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
}

# Now, shared drives for the home resources 
if ($wssMapped ) { 
   Echo "Skipping Home maps on Windows Storage Server WSS-100" 
} else { 
   New-PSDrive -Name M -root \\wss-100\Music    -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
   New-PSDrive -Name P -root \\wss-100\Pictures -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
   New-PSDrive -Name V -root \\wss-100\Videos   -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
}

# Finally, some specialized resources 
   New-PSDrive -Name W -root \\srv1\Working     -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
   New-PSDrive -Name U -root \\srv1\Charlie     -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
   New-PSDrive -Name Y -root \\hp180-ts-17\RemoteApps -scope Global -PSProv FileSystem -Cred $Contoso -Persist 
}

There we go, and I can run this from both elevated and standard user PowerShell windows. The best part is, these mapped drives are visible in that PowerShell window, but also in Windows Explorer, and anywhere else I need a mapped drive.

Charlie.

ETA: We've come a long way in Windows PowerShell v5, and there's a better way to do this. See Mapping Drives Revisited.

Stopping All Running Virtual Machines (Hyper-V)

So, a good friend and fellow MVP asked me for a script to shut down all running virtual machines on a server so she could do cold backups of them. This seemed like a perfectly reasonable request, and my first thought was “Well, this gets really obvious and easy in Windows Server 8” since we have a full set of Hyper-V cmdlets there. But then I sort of remembered doing something like this before, and hunted around and found this old TechNet Wiki article I wrote over a year ago. It wasn’t a full fledged script, but had all the pieces I needed to put together a simple script to stop all the VMs on the local Hyper-V host:

# This is a simple script to stop all the currently running VMs on the local
# Hyper-V host. It could easily be extended to accept a command line
# argument of the name of a remote yper-V hosts or a list of hosts into an array

$VMs = Get-WmiObject MSVM_ComputerSystem -computer "." -namespace "root\virtualization"
ForEach ($vm in $VMs) {
   if ( $vm.name -ne $vm.elementname ) {   # skip the parent's name
      if ( $vm.EnabledState -eq 2 ) {      # If the VM is running
         $shutdown = Get-WmiObject MSVM_ComputerSystem -namespace "root\virtualization" –query “Associators of {$vm} where ResultClass=Msvm_ShutdownComponent”

         $shutdown.iniateShutdown($true,”System Maintenance”)

         sleep 5
      }
   }
}

So, what’s happening in that script? Well, Get-WmiObject grags a list of all the VMs on the local Hyper-V Host (-computer “.”), then we simply loop through the list (skipping host itself ($vm.name -ne $vm.elementname), and for each VM that is running ($vm.EnabledState -eq 2), we get a shutdown object for that specific VM and then call the initiateShutdown method on that object. 

 

Note that this is a “forced shutdown”, so is equivalent to “shutdown –s –f” at the command line. Some processes may not get politely shutdown. Too bad, so sad. Since we need this to work regardless of what else is happening, that’s a necessary risk.

Charlie.

Starting Exchange Services after a Power Failure

In my environment, with a virtualized SBS 2011 Standard, there are occasionally Microsoft Exchange 2010 services that don't properly restart if there has been an abrupt power failure on the Hyper-V host. (Don't ask.)

Now, of course, the first time this happened, I just logged in to the server and started the services. But when it happened again, it's time to write a script. And it was a fun script, since it uses WMI and PowerShell remoting and other fun stuff.

# Script to start Exchange services on SBS 2011 Server after power failure
#
# Accepts a parameter of the exchange server name, but defaults to SRV2 if none entered
# Assumes you are logged in to the domain with Domain Admin credentials
#
# Created: 14/03/2011 by Charlie
# ModHist: 15/03/11 -switched to using WMI in the session to get StartMode
#
#
param ($ExchSrv = "SRV2" )

# first, open a session to the Exchange server
$srv = New-PSSession $ExchSrv

#Now use Invoke-Command with -Session
Invoke-Command -Session $srv -scriptblock {
   $exsvc = gwmi win32_service | Where-Object {$_.Name -like "MsExch*" `
     -and $_.StartMode -eq "Auto"   -and $_.State -eq "Stopped" }
   if ($exsvc ) {
      foreach ($svc in $exsvc ) {
         Start-Service $svc.name
      }
   }
}

The if statement in there is to prevent an error if all the services are running. Of course, for this script to work as it's written, you'll need to run it from a workstation in the SBS domain, and you'll have to enable PowerShell remoting on both the server and the client. If you haven't done that yet, I've posted a quick setup guide on TechNet.

I've posted this script up to the Microsoft Script Center, so if you have comments or suggestions to improve it, please comment there.

Charlie.

PowerShell, Hyper-V and WMI

I’ve started an article over on the PowerShell Survival Guide Wiki to drop in quick hits how to do “stuff” with Hyper-V, using PowerShell and the native WMI interface of Hyper-V. The WMI namespace for Hyper-V is  “root\virtualization”. Turns out managing Hyper-V isn’t as hard as I thought, at least in no small part because working with WMI in PowerShell is actually pretty straightforward. I’m still learning and poking around, but this stuff is all over the net if your bingle skills are good. Today I added the simple steps needed to create a VHD, either dynamic or fixed, to the Wiki page. Any one who wants to join in is more than welcome – that’s what a Wiki is all about after all.

Charlie.