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 "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 {192.168.10.5, fe80::812e:a888:ac40:666b, 2001:db8:0:10::5}
trey-dc-02  {192.168.10.2, fe80::312c:a27c:c87e:3f98, 2001:db8:0:10::2}
trey-wds-11 {192.168.10.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.

Charlie.

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
...<snip>...
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) [==========================100.0%==========================] 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";`
                           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.

More $profile madness

As usual, a minor glitch in my workflow has me mucking around with my $profile. It all started because I normally run Word, Excel, and other Office applications from the command line (as I do many non-MS applications.) To do this, I have functions and aliases in my $profile. These let me start Word, for example, by typing "word" on the PowerShell command line along with any filenames I want to open. So far, so good. But I recently started using Office 2013 on some machines, and this broke my functions for those machines. And at least one of them is running the 640-bit version of office. Hmmm. That could just be a problem. Now I could manually edit the functions in those $profiles, but that's just kludgy. I like to have a single $profile that I load on any new machine I work on. So, rather than the hard-coded paths, which were never really elegant, but worked fine when I only had one version running, it's time to be a bit more general. So, I added the following function to my $profile:
Function Find-Office {
   # if on 32-bit, or running 64-bit office on 64-bit machine, this will find office
   $Global:[[code]]czo2Olwib2ZmaWNlXCI7e1smKiZdfQ==[[/code]] = Get-ChildItem `
              -file "Outlook.exe" `
              -path "C:\Program Files\Microsoft Office\" `
              -recurse
    if (! $office) {
    # If we didn't find it there, look down the (x86) path. But only if we didn't find it
    $Global:[[code]]czo2Olwib2ZmaWNlXCI7e1smKiZdfQ==[[/code]] = Get-ChildItem `
              -file "Outlook.exe" `
              -path "C:\Program Files (x86)\Microsoft Office\" `
              -recurse
    }
}

Now, a couple of comments here -- I made the $Office variable Global so that I could get at the value outside of my profile, since there's a fair amount of information and properties in it.  The other thing is that while I'm searching for Outlook, it really could have been any of the core Office apps that I know get installed every time I install Office. If you're running Office Home & Student edition, however, you'd best choose a different executable, such as "winword.exe".

So, now that we know where Office is, we can extract the path to it:
$OffPath = (Find-Office).DirectoryName

(note, we're still inside my $profile, but we won't bother to make $OffPath global, since we can easily get it again if we need it. )

Finally, the functions that our aliases will call to start the core Office applications:
function Run-Word { & "$OffPath\WinWord.exe" $args}
function Run-Excel { & "$OffPath\Excel.exe" $args }
function Run-OneNote { & "$OffPath\OneNote.exe" $args }
function Run-Outlook { & "$OffPath\Outlook.exe" $args }

The aliases are simply calls to these functions in the form:
set-alias word Run-Word

Nothing fancy there. But now I've got easy command line access to my core Office applications. (As you can see, I don't use PowerPoint any more than I have to. :))

OneNote 2010 and Windows Server 2012 RD Session Host

You can install Office 2010 in an RD Session Host (ie, Terminal Server), if you have a volume license version. (If you're using MSDN versions for testing, there's a special key for use in an RD Session Host environment see: http://support.microsoft.com/kb/983443.)

Great, and you can go ahead and use all the various features of Office. Except OneNote, which fails with an error message telling you to install the Desktop Experience Feature since you're on Windows Server. Well, that's great, and if you're on Server 2008 R2, that's the solution. BUT, if you're running on Windows Server 2012, there is no longer a Desktop Experience Feature. It's GONE. However, what OneNote is looking for is "Ink and Handwriting Services ".

To solve the problem, and install the necessary feature, run:
PS C:\> Add-WindowsFeature InkAndHandwritingServices

This will automatically load the ServerManager module (isn't PowerShell v3 cool?!) and add the necessary feature. This WILL need a reboot, so be prepared.

Charlie.

 

Windows Azure PowerShell

For those of you using Windows Azure, we finally have official PowerShell support for managing your Azure subscriptions. With the CTP release of the 1.7 SDK, you can install the Windows Azure PowerShell cmdlets. The download is here.

 

Keep in mind that this is a CTP, and the first version of the cmdlets released,  so there are a few rough edges and missing functionality still. But they are already very usable and a big improvement if you need to manage more than a single image in Windows Azure. As I get time, I'll be posting more stuff here, but I also recommend you add Michael Washam's blog to your favourites.

 

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.