WMI

Several years ago, I wrote a fairly simplistic script to get the free disk space of remote computers. It wasn't all that sophisticated, but it got the job that I needed done, so I shared it here on my blog, since I thought others might find it useful. Which, based on the number of hits here, and the comments, they did. However, based on some of those comments, it had a problem for some users.

The problem was that I used Write-Host in it. That was fine for me, because I only used it to write to my screen. But it's a bad practice to be using Write-Host unless you really need to manipulate screen colours. The reason it's a bad practice is that it prevents any sort of redirection! This meant that those users who wanted to capture the result of the script in a file were horked, because Write-Host will ALWAYSwrite to ( ... wait for it... )

The Host. You can't redirect it. The fix, of course, is easy -- use Write-Object instead, which is what I should have done in the first place.

While I was in the process of making that change, I thought it would be nice to add in a basic Get-Help page for it, which was trivial. But then it occurred to me that I really should let it handle pipeline input, allowing me to use other PowerShell commands to select the group of machines I wanted the free disk space on, and then pipe that result directly to Get-myFreeSpace.

Seemed like a good idea, but it turned out I had to almost completely rewrite the script to use the Begin{}/Process{}/End{} syntax. Accepting pipeline input is not as simple as just saying you do in the Parameter statement, you need to actually process that input. The result is the new, improved version of Get-myFreeSpace.ps1 shown below. (If you care about how I got to this script in the first place, do check out the original post, here. There's some useful information there about the whole process. )

And there you have it. A new and improved version of one of the most popular scripts I've ever posted here. You can use it to get the disk utilization on your current machine, or any list of remote computers to which you have the rights to run WMI against.

I hope you find this script useful, and I'd love to hear comments, suggestions for improvements, or bug reports as appropriate. As always, if you use this script as the basis for your own work, please respect my copyright and provide appropriate attribution.

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:

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:

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:

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.

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.

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

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

ETA:The Console as we know it is completely brokenin recent builds of Windows 10. As of August, 2018, you can't effectively set the background color of the console and have it stick, plus they completely changed the behaviour of Set-PSReadlineOption by changing the parameters, and removing support for -TokenKind with FG/BG colors to totally break this script. Sigh.

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.

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

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:

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:

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:

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:

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.

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.

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.

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.

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.

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 Hyper-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.

ETA: Wow, this is an old script. Written before we had built-in Hyper-V cmdlets to do this. This still works, and isn't version dependent so far as I know, but I've written updated versions of this a couple of times since. The most recent was 2016, here. You might want to check that post out. It's a modern, PowerShell v5 version.

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

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.