A More Readable Path

The path on many modern computers gets to be so long that reading it is a nuisance. Worse, from within Windows PowerShell, there isn’t a built in equivalent to the old DOC “path” command. Yet all the information we need is there, it’s just not where we might expect it. The actual executable path for a PowerShell session is stored in $ENV:path. Here’s a quick function you put in your $profile to print out the PATH, one line for each item:

Function path { 

Simple, wasn't it? Now, when you type "path" from the Windows PowerShell prompt, you'll see something like this:

PSH> path 

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.

Unmaps network drives
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. 

Unmaps all currently mapped network drives 

    Author: Charlie Russel
 Copyright: 2015 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 06/27/2015 (cpr)

# 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"

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!)

Finally, the death of CMD!

Well, OK, it’s still there in Windows 10 But finally, completely, I no longer need it for ANYTHING! The final piece that I still used is now available in PowerShell v5. The latest publicly available Preview Build of Windows 10 Technical Preview (build 9926), includes updates to New-Item to support Junctions and Hard Links. Finally!


There’s no direct help available for it yet (I checked and downloaded the very latest), but a little poking around produced:

PS> New-Item -Type HardLink -Path 2ndTest.txt -Value Test.txt

I can edit 2ndTest.txt and the changes appear in Test.txt, and I can delete Test.txt and 2ndTest.txt is still there and editable. Interestingly, I do NOT need to run this as an administrator, assuming I have permissions in the folder where I’m creating the second (or third or 15th file). And, as expected, this can’t cross drive boundaries.


This build also includes support for directory Junctions, (-Type Junction) so between these two, I think we finally have all we need to kiss goodbye all further use of cmd.exe. (I really hate having to use cmd c/ from my PowerShell to get something done. Really. It’s so DOS.)


My thanks to the PowerShell team who took my challenge and mantra of Death To CMD to heart and have finally made Windows PowerShell the tool I can use for everything.

(Note: Currently the most current build available for download separately is the November build which doesn’t have these two New-Item features, so the only way to get them right now is in the latest Windows 10 build. But that’s likely to change soon.)


ETA: The February WMF 5.0 build is available from Windows Download Centre. This includes support for Windows Server 2012, Windows Server 2012 R2, and Windows 8.1 Pro and Enterprise. And yes, the changes to New-Item are in there. :)


Oh, and while we’re at it – there’s all sorts of other goodies in PowerShell v5, including OneGet, a great way to get new modules for your PowerShell. Check it out.

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

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:

Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  @{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 

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:

Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  @{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 % 

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 
#        : 
# ********************************************* 
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 ` 
            Expression={"{0:N0}" -f ($_.FreeSpace/1GB)};` 
         @{Label="% Free";` 
            Expression={"{0:P0}" -f ($_.FreeSpace / $_.Capacity)};` 
            Expression={"{0:N0}" -f ($_.Capacity / 1GB)};` 
         @{Label="Volume Label";` 

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 

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 "Computer $ComputerName has $nSlots Memory Slots, and a Maximum 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 the IP addresses of running VMs


Ben Armstrong posted a great little tip on his blog the other day. He has a little one-line PowerShell command that gives you a listing of all the running VMs on a host, and the IP addresses being used by each of them.

Get-VM | ?{$_.State -eq "Running"}   Get-VMNetworkAdapter | Select VMName, IPAddresses

Add the -ComputerName parameter, with the name of your Hyper-V server in the above, and you've got a really useful little script to figure out which VM has an address it shouldn't. Of course, I just had to tweak it a bit, by changing that last Select to a simple format-table, which allows me to get rid of the unnecessary whitespace with a -auto parameter. Thus:

PSH> Get-VM -computername cpr-asus-lap | ?{$_.State -eq "Running"} | Get-VMNetworkAdapter | ft -auto VMName, IPAddresses
VMName      IPAddresses
------      -----------
trey-cdc-05 {, fe80::812e:a888:ac40:666b, 2001:db8:0:10::5}
trey-dc-02  {, fe80::312c:a27c:c87e:3f98, 2001:db8:0:10::2}
trey-wds-11 {, fe80::4520:ea29:54bb:9b41, 2001:db8:0:10::b}

Thanks, Ben. That's a useful one!

Shutting Down Running VMs

A quick Hyper-V PowerShell one-liner today. This one will gracefully stop all virtual machines with RODC in their name.

 Get-VM -Name *rodc* | Where-Object {$_.State -eq "Running" } | Foreach-Object { Stop-VM $_.Name }

I needed this because I was cloning an RODC that I had virtualized, and I wanted a quick way to shut it down gracefully without actually logging on to it. (It's on a private subnet that is a nuisance for some things.) Then I simply extended it a bit to get all my RODC's and to make sure that I didn't try to stop a VM that was already stopped.


And, because I'm lazy and don't want to bother logging in to my VM host server, I modified this to get all the running VMs on the server and then stop them.

Get-VM -ComputerName Trey-VMHost `
          | Where-Object {$_.State -eq "Running" } `
          | Foreach-Object { Stop-VM $_.Name -ComputerName $_.ComputerName }

Notice that by the time I get to the end of the pipeline, I need to tell Stop-VM what computer these are running on. No problem, that's part of the object I'm passing in to it anyway. And, you could easily extend this by providing an array of host names to Get-VM to stop multiple VMs on multiple hosts.

Set Network Location to Private in Windows 8.1/Server 2012 R2

One of those annoyances that sometimes happen with the new Network Location in Windows 8.x is that the network gets mis-identified as Public when it should be Private, or the other way around. Changing this in the GUI is certainly possible, but annoying, so let's take advantage of the improved Windows PowerShell support in Windows 8.1 and do it quickly and easily. First, let's open up an elevated PowerShell window from our limited user session:

PSH> Start-Process WindowsPowerShell.exe -verb RunAs

Now, in that window, let's find out what our current network location is set to:

PSH> Get-NetConnectionProfile
Name : Unidentified network
InterfaceAlias : vEthernet (Local-10)
InterfaceIndex : 18
NetworkCategory : Public
IPv4Connectivity : LocalNetwork
IPv6Connectivity : LocalNetwork

From this, we see that the problem interface has an Interface Index of 18, so:

PSH> Set-NetConnectionProfile  -InterfaceIndex 18 -NetworkCategory Private

And we're done.

Installing .Net 3.5 (and earlier) on Windows 8 and Windows 8.1

There are some older applications that require earlier versions of .NET than the version included and enabled by default on Windows 8 and Windows 8.1. Normally you can enable the .NET Framework 3.5 (which includes .NET 2.0 and .NET 3.0) as a Windows Feature. (Control Panel, Programs and Features, Turn Windows Features on or off.) But in some environments, especially those using SBS and WSUS, you may get an error message:

0x800f0906: “Windows couldn't connect to the Internet to download necessary files. Make sure that you're connected to the Internet, and click Retry to try again.”

This, of course, is totally bogus because the computer IS connected to the Internet, but save yourself some grief and do it from the command line in the first place. The files you need are part of the DVD (or USB stick or whatever) that you used to install Windows. Just insert that media and from an elevated command or PowerShell prompt type:

PSH> DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:E:\Sources\SxS

Deployment Image Servicing and Management tool
Version: 6.3.9600.16384

Image Version: 6.3.9600.16384

Enabling feature(s)
The operation completed successfully.

Where the E: drive in E:\Sources\SxS  is replaced by the drive letter of the DVD, USB or other media source or your Windows distribution media. Or, the pure PowerShell way:

PSH> Enable-WindowsOptionalFeature -Online -FeatureName "NetFx3" -All -LimitAccess -Source "E:\Sources\SxS"

Now, whatever application it is that needs this older version of the .NET Framework will be happy. (Zune 4.8, in my case.)

Updated: 13 November 2013: Added PSH equivalent.
:   2 June, 2014: Fixed curly quotes
:   13 October, 2014: Confirmed, the same command works in Windows 10 Preview


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";`
                        @{Label="Volume Label";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`
 $OpticalDrives | Sort Drive | ft -auto `
                           Expression={ if ($_.Capabilities -eq 4) { `
                             "Read/Write" `
                           } else { `
                        @{Label="Disk Volume Label";`
 $RemoteDrives | ft -auto @{Label="Drive";`
                        @{Label="Remote Share";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`


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