System Center Configuration Manager remote tools side-by-side

At work, I’m currently running a side-by-side migration from the System Center Configuration 2007 (aka SCCM 2007) to System Center Configuration Manager 2012 (aka ConfigMgr 2012). The helpdesk team have been accustomed to launch a rsms.bat file that I wrote years ago, to launch directly the remote tools. It had a great advantage, as the support operator doesn’t need to launch the Admin Console, expand the collection, search the target computer and right-click the computer to launch the remote tools. This old nifty script allowed the helpdesk operator to gain precious seconds while answering phone calls.

Here’s the content of my brave old rsms.bat file:

@echo off

set _HERE=%0\..
set _HERE=%_HERE:"=%
set _TIP=

for /f  "tokens=4,*" %%i in ('%systemroot%\system32\reg.exe query "HKLM\SOFTWARE\Microsoft\ConfigMgr\Setup" /v "UI Installation Directory" ^| find /i "REG_SZ"') do @ set _REXEPATH=%%j
set _REXE=%_REXEPATH%\bin\i386\rc.exe
if not exist "%_REXE%" echo ERROR: "%_REXE%" not found & goto finish

set _target=%1

:: Sanity check
if "%_target%"=="" echo ERROR: Target Computer not specified as first argument&  goto finish

:: Try to see if machine is up and get its IP address
ping %_TARGET% -n 1 | find /i "TTL" >NUL 2>&1
if not "%errorlevel%"=="1" goto _ok
echo %1: not available& goto finish

:_ok
:: Get the target IP Address
for /F "tokens=1-3 delims=: " %%i in ('ping %_TARGET% -n 1 ^| findstr /i /c:"Reply from"') do set _TIP=%%k

if "%_TIP%"=="" echo.& echo Unable to get the IP address & echo. & echo Use the following command: rsms.bat IPaddress where IPaddress is the following IP & echo. & ping %_TARGET% & goto finish

echo Connecting to %_TARGET% = %_TIP%

:: Expands %i to a full path name with short names only
for /F "tokens=*"  %%i in ( "%_REXEPATH%" ) do @ start %%~fsi\bin\i386\rc.exe 1 %_TIP% \\NameOfSCCM2007Server\

:finish

To support both plateforms side-by-side, I’ve replaced its content and actually moved the master piece into a powershell script as follows 🙂

@echo off
if "%1"=="" echo Target is missing & goto finish
if not exist %systemroot%\rsms.ps1 echo The file "rsms.ps1" is missing & goto finish
if not exist %systemroot%\system32\WindowsPowershell\v1.0\powershell.exe echo Powershell not found & goto finish
"%systemroot%\system32\WindowsPowershell\v1.0\powershell.exe" -ExecutionPolicy RemoteSigned -File %systemroot%\rsms.ps1 %1
:finish
# Requires -Version 2.0            
[CmdletBinding()]            
Param(            
    [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]                        
    [Alias("CN","__SERVER","IPAddress")]                        
    [String]$ComputerName            
)            
Begin {            
    # Step 1: Check if we can find locally the admin console for:            
    # SCCM 2007            
    try {            
        $ConfigMgr2007ConsolePath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ConfigMgr\Setup" -Name "UI Installation Directory" -ErrorAction Stop)."UI Installation Directory"            
    } catch {            
        Write-Verbose -Message "Failed to find the registry key for SCCM 2007 Admin console"            
    }            
    # SCCM 2012            
    try {            
        $ConfigMgr2012ConsolePath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\ConfigMgr10\Setup" -Name "UI Installation Directory" -ErrorAction Stop)."UI Installation Directory"            
    } catch {            
        Write-Verbose -Message "Failed to find the registry key for SCCM 2012 Admin console"            
    }            
}             
Process {            
    # Step 2: Test the remote computer using WMI to figure out what ConfigMgr client it has installed            
    try {            
        $SCCMClient = Get-WmiObject -Namespace root/CCM -Class SMS_Client -ComputerName $ComputerName -ErrorAction Stop            
    } catch {            
        # WMI was unable to retrieve the information            
        Switch ($_) {            
            { $_.Exception.ErrorCode -eq 0x800706ba } {            
                $reason =  "Unavailable (offline, firewall)"            
            }            
            { $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } {            
                $reason = "Access denied"            
            }            
            default {             
                $reason  = $_.Exception.Message            
            }            
        }            
        Write-Warning -Message "Failed to connect to $($Computername) because: $reason"            
        break            
    }            
    # Step 3: Based on the SCCM client version launch the remote tools with the appropriate Admin Console            
    if ($SCCMClient) {            
        Write-Verbose -Message "SCCM Client version $($SCCMClient.ClientVersion) found on $($ComputerName)" -Verbose            
        Switch -regex (($SCCMClient.ClientVersion)) {            
            "4\.00\.\d{4}\.\d{4}" {            
                if (Test-Path "$($ConfigMgr2007ConsolePath)\bin\i386\rc.exe") {            
                    #             
                    & (Get-Command "$($ConfigMgr2007ConsolePath)\bin\i386\rc.exe") @("1",$ComputerName)            
                } else {            
                    Write-Warning -Message "SCCM 2007 Remote tools not found to target the SCCM Client $ComputerName"            
                }            
                break            
            }            
            "5\.00\.7804.\d{4}" {            
                if (Test-Path "$($ConfigMgr2012ConsolePath)\bin\i386\CmRcViewer.exe") {            
                    #             
                    & (Get-Command "$($ConfigMgr2012ConsolePath)\bin\i386\CmRcViewer.exe") @($ComputerName)            
                } else {            
                    Write-Warning -Message "SCCM 2012 Remote tools not found to target the SCCM Client $ComputerName"            
                }            
                            
                break            
            }            
            default {            
                Write-Warning "The Client version of SCCM $($_) is unexpected"            
            }            
        }            
    }            
            
}            
End {}

Here’s what the operator can see when he targets different computers. Of course, he has previously installed both SCCM 2007 and ConfigMgr Admin consoles and been granted the ‘remote tools’ privilege on both System Center Configuration Manager servers…

Advertisements

Pagefile configuration and guidance

I’ve used for years the registry key HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management to set a custom pagefile configuration on a server or a computer whenever required.

PowerShell can also be used to set the pagefile configuration. The Microsoft All-In-One Script members published on the Technet gallery a Script to configure virtual memory page file size (PowerShell)
The old fashioned way with the WMI commandline utility is another option. See for example: http://marckean.wordpress.com/2012/09/26/windows-server-2012-hyper-v-server-command-line-configuration-2/

However I don’t recommend to use the WMI Win32_Pagefile class on Windows Server 2012 because it’s deprecated.

([wmiclass]'Win32_PageFile').GetText("MOF")

Let’s try to discover how to set it without the Win32_Pagefile WMI class. We can start enumerating other WMI classes that have the occurence of the word “pagefile” in their name:

Get-CimClass -ClassName *PageFile*


We can list all the properties of the Win32_PageFileUsage WMI class:

Get-CimInstance Win32_PageFileUsage | fl  *


The Allocatedbasesize property (768 in my example) is actually the size of the page file on the disk expressed in MB:

(Get-ChildItem c:\ -Attributes H,S | ? Name -eq "pagefile.sys").Length /1MB

You should also be aware that by default, the pagefile is

and this flag is actually a property of the Win32_ComputerSystem WMI class.

gwmi Win32_ComputerSystem | fl AutomaticManagedPagefile            
Get-CimInstance CIM_ComputerSystem | gm -Name AutomaticManagedPagefile


As you can see in the above picture, this flag can be written. For sure, Powershell will allow us to configure the pagefile using WMI or CIM.

Now that we can foresee how to set it, let’s try to figure out how much to set. Finding the appropriate guidance is the most difficult part and I’ll prove it.

The Windows XP recommendation that you can find on this page

For best performance, do not set the initial size to less than the minimum recommended size under Total paging file size for all drives. The recommended size is equivalent to 1.5 times the amount of RAM on your system.

has been erected as a somehow immutable law. Even if the Microsoft All-In-One Script members powershell script uses it, it doesn’t hold true anymore.

For XP and Windows 2003 server, there’s the following KB article, How to determine the appropriate page file size for 64-bit versions of Windows http://support.microsoft.com/kb/889654

Important Supportability Information: This article is specifically for computers that do not need kernel mode or full memory dump analysis. For business-critical servers where business processes require to server to capture physical memory dumps for analysis, the traditional model of the page file should be at least the size of physical ram plus 1 MB, or 1.5 times the default physical RAM. This makes sure that the free disk space of the operating system partition is large enough to hold the OS, hotfixes, installed applications, installed services, a dump file, and the page file. On a server that has 32 GB of memory, drive C may have to be at least 86 GB to 90 GB. This is 32 GB for memory dump, 48 GB for the page file (1.5 times the physical memory), 4 GB for the operating system, and 2 to 4 GB for the applications, the installed services, the temp files, and so on. Remember that a driver or kernel mode service leak could consume all free physical RAM. Therefore, a Windows Server 2003 x64 SP1-based server in 64-bit mode with 32GB of RAM could have a 32 GB kernel memory dump file, where you would expect only a 1 to 2 GB dump file in 32-bit mode. This behavior occurs because of the greatly increased memory pools.

For Windows 2008 and 2008 R2 servers, there’s the following KB article: How to determine the appropriate page file size for 64-bit versions of Windows Server 2008 and or Windows 2008 R2 http://support.microsoft.com/kb/2021748
See the Table 2. Default Page File Sizes for Windows Vista, Windows Server 2008, Windows 7 and Windows Server 2008 R2.
Source: “Windows Internals” 5th edition Chapter 9: Memory Management Page 781.

There is no specific recommendation for page file size. Your requirements will be based on the hardware and software that you use and the load that you put on the computer.

Note Page file use should be tracked periodically. When you increase the use or the load on the system, you generally increase the demand for virtual address space and page file space.

To determine the approximate minimum page file that is required by your system, calculate the sum of peak private bytes that are used by each process on the system. Then, subtract the amount of memory on the system.

To determine the approximate maximum page file space that is required for your system, calculate the sum of peak private bytes that are used by each process on the system. Then, add a margin of additional space. Do not subtract the amount of memory on the system. The size of the additional margin can be adjusted based on your confidence in the snapshot data that is used to estimate page file requirements.

..and there’s the same “supportability” notice as the one for XP/2003.

Let me also mention 3 other sources:

  • Best Practices for Setting Up Page File and Minimum Drive Size Required for OS Partition on Windows Servers by Santosh Bhandarkar, a MSFT partner.
  • Pushing the Limits of Windows: Virtual Memory by Mark Russinovich
  • How Big Should I Make the Paging File?

    Perhaps one of the most commonly asked questions related to virtual memory is, how big should I make the paging file? There’s no end of ridiculous advice out on the web and in the newsstand magazines that cover Windows, and even Microsoft has published misleading recommendations. Almost all the suggestions are based on multiplying RAM size by some factor, with common values being 1.2, 1.5 and 2. Now that you understand the role that the paging file plays in defining a system’s commit limit and how processes contribute to the commit charge, you’re well positioned to see how useless such formulas truly are.

    Since the commit limit sets an upper bound on how much private and pagefile-backed virtual memory can be allocated concurrently by running processes, the only way to reasonably size the paging file is to know the maximum total commit charge for the programs you like to have running at the same time. If the commit limit is smaller than that number, your programs won’t be able to allocate the virtual memory they want and will fail to run properly.

  • Windows 8 and Windows Server 2012: Automatic Memory Dump by the Windows Server Core Team

Confused? Let’s experiment and mess with it.
If I disable the pagefile, I’ve got the following warning:

Note that the amount of required RAM may vary.

After a reboot, there’s a second warning saying:

The temporary pagefile created on the disk is around 1GB.

Let’s fix this with the following code, a fork of the script from the Microsoft All-In-One Script members published on the Technet gallery

#Requires -Version 3.0

Function Set-PageFile {
<#
 	.SYNOPSIS
        Set-PageFile is an advanced function which can be used to adjust virtual memory page file size.
    .DESCRIPTION
        Set-PageFile is an advanced function which can be used to adjust virtual memory page file size.
    .PARAMETER  <InitialSize>
		Setting the paging file's initial size.
	.PARAMETER  <MaximumSize>
		Setting the paging file's maximum size.
	.PARAMETER  <DriveLetter>
		Specifies the drive letter you want to configure.
	.PARAMETER  <SystemManagedSize>
		Allow Windows to manage page files on this computer.
	.PARAMETER  <None>		
		Disable page files setting.
	.PARAMETER  <Reboot>		
		Reboot the computer so that configuration changes take effect.
	.PARAMETER  <AutoConfigure>
		Automatically configure the initial size and maximumsize.
    .EXAMPLE
        C:\PS> Set-PageFile -InitialSize 1024 -MaximumSize 2048 -DriveLetter "C:","D:"

		Execution Results: Set page file size on "C:" successful.
		Execution Results: Set page file size on "D:" successful.

		Name            InitialSize(MB) MaximumSize(MB)
		----            --------------- ---------------
		C:\pagefile.sys            1024            2048
		D:\pagefile.sys            1024            2048
		E:\pagefile.sys            2048            2048
	.LINK
		Get-WmiObject
		http://technet.microsoft.com/library/hh849824.aspx
#>
	[cmdletbinding(SupportsShouldProcess,DefaultParameterSetName="SetPageFileSize")]
	Param
	(
		[Parameter(Mandatory,ParameterSetName="SetPageFileSize")]
		[Alias('is')]
		[Int32]$InitialSize,

		[Parameter(Mandatory,ParameterSetName="SetPageFileSize")]
		[Alias('ms')]
		[Int32]$MaximumSize,

		[Parameter(Mandatory)]
		[Alias('dl')]
        [ValidatePattern('^[A-Z]$')]
		[String[]]$DriveLetter,

		[Parameter(Mandatory,ParameterSetName="None")]
		[Switch]$None,

		[Parameter(Mandatory,ParameterSetName="SystemManagedSize")]
		[Switch]$SystemManagedSize,

		[Parameter()]
		[Switch]$Reboot,

		[Parameter(Mandatory,ParameterSetName="AutoConfigure")]
		[Alias('auto')]
		[Switch]$AutoConfigure
	)
Begin {}
Process {
    If($PSCmdlet.ShouldProcess("Setting the virtual memory page file size")) {
        $DriveLetter | ForEach-Object -Process {
            $DL = $_
            $PageFile = $Vol = $null
            try {
                $Vol = Get-CimInstance -ClassName CIM_StorageVolume -Filter "Name='$($DL):\\'" -ErrorAction Stop
            } catch {
                Write-Warning -Message "Failed to find the DriveLetter $DL specified"
                return
            }
            if ($Vol.DriveType -ne 3) {
                Write-Warning -Message "The selected drive should be a fixed local volume"
                return
            }
            Switch ($PsCmdlet.ParameterSetName) {
                None {
                    try {
			            $PageFile = Get-CimInstance -Query "Select * From Win32_PageFileSetting Where Name='$($DL):\\pagefile.sys'" -ErrorAction Stop
                    } catch {
                        Write-Warning -Message "Failed to query the Win32_PageFileSetting class because $($_.Exception.Message)"
                    }
		            If($PageFile) {
                        try {
				            $PageFile | Remove-CimInstance -ErrorAction Stop 
                        } catch {
                            Write-Warning -Message "Failed to delete pagefile the Win32_PageFileSetting class because $($_.Exception.Message)"
                        }
		            } Else {
			            Write-Warning "$DL is already set None!"
		            }
                    break
                }
                SystemManagedSize {
	                Set-PageFileSize -DL $DL -InitialSize 0 -MaximumSize 0
                    break
                }
                AutoConfigure {			
                    $TotalPhysicalMemorySize = @()
		            #Getting total physical memory size
                    try {
		                Get-CimInstance Win32_PhysicalMemory  -ErrorAction Stop | ? DeviceLocator -ne "SYSTEM ROM" | ForEach-Object {
                            $TotalPhysicalMemorySize += [Double]($_.Capacity)/1GB
                        }
		            } catch {
                        Write-Warning -Message "Failed to query the Win32_PhysicalMemory class because $($_.Exception.Message)"
                    }		
		            <#
		            By default, the minimum size on a 32-bit (x86) system is 1.5 times the amount of physical RAM if physical RAM is less than 1 GB, 
		            and equal to the amount of physical RAM plus 300 MB if 1 GB or more is installed. The default maximum size is three times the amount of RAM, 
		            regardless of how much physical RAM is installed. 
		            If($TotalPhysicalMemorySize -lt 1) {
			            $InitialSize = 1.5*1024
			            $MaximumSize = 1024*3
			            Set-PageFileSize -DL $DL -InitialSize $InitialSize -MaximumSize $MaximumSize
		            } Else {
			            $InitialSize = 1024+300
			            $MaximumSize = 1024*3
			            Set-PageFileSize -DL $DL -InitialSize $InitialSize -MaximumSize $MaximumSize
		            }
                    #>


                    $InitialSize = (Get-CimInstance -ClassName Win32_PageFileUsage).AllocatedBaseSize
                    $sum = $null
                    (Get-Counter '\Process(*)\Page File Bytes Peak' -SampleInterval 15 -ErrorAction SilentlyContinue).CounterSamples.CookedValue | % {$sum += $_}
                    $MaximumSize = ($sum*70/100)/1MB
                    if ($Vol.FreeSpace -gt $MaximumSize) {
                        Set-PageFileSize -DL $DL -InitialSize $InitialSize -MaximumSize $MaximumSize
                    } else {
                        Write-Warning -Message "Maximum size of page file being set exceeds the freespace available on the drive"
                    }
                    break
                    			
                }
                Default {
                    if ($Vol.FreeSpace -gt $MaximumSize) {
                        Set-PageFileSize -DL $DL -InitialSize $InitialSize -MaximumSize $MaximumSize
                    } else {
                        Write-Warning -Message "Maximum size of page file being set exceeds the freespace available on the drive"
                    }
                }
            }
        }

	    # Get current page file size information
        try {
	        Get-CimInstance -ClassName Win32_PageFileSetting -ErrorAction Stop |Select-Object Name,
	    @{Name="InitialSize(MB)";Expression={if($_.InitialSize -eq 0){"System Managed"}else{$_.InitialSize}}}, 
	    @{Name="MaximumSize(MB)";Expression={if($_.MaximumSize -eq 0){"System Managed"}else{$_.MaximumSize}}}| 
	    Format-Table -AutoSize
        } catch {
            Write-Warning -Message "Failed to query Win32_PageFileSetting class because $($_.Exception.Message)"
        }
        If($Reboot)	{
	        Restart-Computer -ComputerName $Env:COMPUTERNAME -Force
        }
    }
}
End {}
}

Function Set-PageFileSize {
[CmdletBinding()]
Param(
		[Parameter(Mandatory)]
		[Alias('dl')]
        [ValidatePattern('^[A-Z]$')]
		[String]$DriveLetter,

		[Parameter(Mandatory)]
        [ValidateRange(0,[int32]::MaxValue)]
		[Int32]$InitialSize,

		[Parameter(Mandatory)]
        [ValidateRange(0,[int32]::MaxValue)]
		[Int32]$MaximumSize
)
Begin {}
Process {
	#The AutomaticManagedPagefile property determines whether the system managed pagefile is enabled. 
	#This capability is not available on windows server 2003,XP and lower versions.
	#Only if it is NOT managed by the system and will also allow you to change these.
    try {
        $Sys = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop 
    } catch {
        
    }

	If($Sys.AutomaticManagedPagefile) {
        try {
		    $Sys | Set-CimInstance -Property @{ AutomaticManagedPageFile = $false } -ErrorAction Stop
            Write-Verbose -Message "Set the AutomaticManagedPageFile to false"
        } catch {
            Write-Warning -Message "Failed to set the AutomaticManagedPageFile property to false in  Win32_ComputerSystem class because $($_.Exception.Message)"
        }
	}
	
	# Configuring the page file size
    try {
	    $PageFile = Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($DriveLetter):'" -ErrorAction Stop
	} catch {
        Write-Warning -Message "Failed to query Win32_PageFileSetting class because $($_.Exception.Message)"
    }

	If($PageFile){
        try {
			$PageFile | Remove-CimInstance -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to delete pagefile the Win32_PageFileSetting class because $($_.Exception.Message)"
        }
	}
    try {
	    New-CimInstance -ClassName Win32_PageFileSetting -Property  @{Name= "$($DriveLetter):\pagefile.sys"} -ErrorAction Stop | Out-Null
	 
        # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394245%28v=vs.85%29.aspx			
	    Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($DriveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{
            InitialSize = $InitialSize ;
            MaximumSize = $MaximumSize ; 
        } -ErrorAction Stop
	    
        Write-Verbose -Message "Successfully configured the pagefile on drive letter $DriveLetter"

	} catch {
	    Write-Warning "Pagefile configuration changed on computer '$Env:COMPUTERNAME'. The computer must be restarted for the changes to take effect."
    }
}
End {}
}

Loading automatically the ConfigMgr 2012 SP1 PowerShell module

If you want to load the Configuration Manager 2012 SP1 PowerShell module, you can do it from the upper menu of the admin console:

To discover what it does exactly, I did:

gwmi win32_process | fl commandline

And obtained the following:
commandline : “C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe”
-noExit -ExecutionPolicy AllSigned -Command “Import-Module
‘C:\Program Files (x86)\Microsoft Configuration
Manager\AdminConsole\bin\ConfigurationManager.psd1’; CD MYSITECODE:” |
Invoke-Expression

The above command line triggers an addtional prompt about code signing:

What else do we know about the above command line? There are actually 2 caveats:

  • The module can only be loaded from a 32bit powershell console. As you can see, if you try to load it under a 64bit powershell console, you’ll have:

    Import-Module : PowerShell is not supported in 64-bit version. Run PowerShell in x86 version and import the module
  • All the ConfigMgr cmdlets should be executed when you’re on the provider drive, otherwise you may encounter the following message:
    This command cannot be run from the current drive. To run this command you must first connect to a Configuration Manager drive.

I usually launch the powershell console from the taskbar, which is 64bit console and thus the wrong console 😦

What can I do? Well, I propose to launch it using the current user powershell profile named $profile as it’s shared between the 64 and 32bit consoles.

  • First step: I created a profile
  • notepad $profile

    (NB: the path must exist before creating the file)

  • Second step: Change the execution policy to “RemoteSigned” because my current profile isn’t digitally signed.
  • Third step: Copy/paste the following code in the “${env:userprofile}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1”
# Requires -Version 3.0            
            
$ConfigMgrConsoleCode = @'
try{
    $ConfigMgrPath = Split-Path -Path "${Env:SMS_ADMIN_UI_PATH}" -ErrorAction Stop
} catch {
    Write-Warning -Message "Failed to find ConfigMgr environment variable because $($_.Exception.Message)"
}
if ($ConfigMgrPath) {
    $ConfigMgrModuleLoaded = $false
    $ConfigMgrConsoleFile = Join-Path -Path $ConfigMgrPath -ChildPath "ConfigurationManager.psd1"
    if (Test-Path -Path $ConfigMgrConsoleFile) {
        try {
            Write-Verbose -Message "Loading ConfigMgr module from $ConfigMgrConsoleFile" -Verbose
            Import-Module $ConfigMgrConsoleFile -ErrorAction Stop
            $ConfigMgrModuleLoaded = $true
        } catch {
            Write-Warning -Message "Failed to load ConfigMgr 2012 SP1 module because $($_.Exception.Message)"
        }
        if ($ConfigMgrModuleLoaded) {
            # Change the title of the console
            $Host.UI.RawUI.WindowTitle = "$($Host.UI.RawUI.WindowTitle) 32bit (running as $($env:USERNAME))"
            try {
                $ConfigMgrPSProvider = Get-PSDrive -PSProvider CMSite -ErrorAction Stop
            } catch {
                Write-Warning -Message "Failed to find the PSProvider for ConfigMgr 2012 SP1"    
            }
            if ($ConfigMgrPSProvider) {
	        'Your ConfigMgr Site server is: {0}' -f ($ConfigMgrPSProvider).SiteServer
                'Your ConfigMgr Site   code is: {0}' -f ($ConfigMgrPSProvider).SiteCode
                cd "$($ConfigMgrPSProvider.SiteCode):\"
                try {
                    Update-Help -Module ConfigurationManager -Force -ErrorAction Stop
                } catch {
                    Write-Warning -Message "Failed to update the help of the ConfigurationManager module because $($_.Exception.Message)"
                }
            }
        }
    } else {
        Write-Warning -Message "ConfigMgr 2012 SP1 module not found"
    }
}
'@            
if ($pshome -eq "$($env:windir)\SysWOW64\WindowsPowerShell\v1.0") {            
    Write-Verbose -Message "Trying to load ConfigMgr 2012 SP1 module if appropriate" -Verbose            
    Invoke-Expression -Command $ConfigMgrConsoleCode            
} else {            
    Write-Verbose -Message "Launching a 32bit powershell console to load ConfigMgr 2012 SP1 module" -Verbose            
    Start-Process -FilePath "$($env:windir)\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe" -Verb Runas             
}

Now, whenever I launch the 64bit powershell console from the taskbar, elevated or not, I’ve got a second powershell 32bit console poping up that loads automatically the ConfigMgr 2012 SP1 PowerShell module, and as a bonus…

…it updates automatically the help associated with the module:

Now, I’m feeling more confortable to work with it 😎 as old habits die hard.

Deciphering WinRM error codes

This morning I learned a nice tip from Shay Levy about Decoding WinRM error messages

I wanted to achieve the same thing without using the winrm.vbs file as vbscript isn’t allowed in my environment.
I opened the winrm vbscript and looked how the error code is translated.

The string is first converted to hexadecimal and then to a long integer.
Finally it uses a COM object to translate the error to a human readable error description.

I remembered that I’ve already worked on this hexadecimal to long integer conversion.
https://p0w3rsh3ll.wordpress.com/2012/08/21/deciphering-error-codes/

I first tried Boe’s approach but it failed 😦

[ComponentModel.Win32Exception]0x80338104

Then I looked at this WSMan.Automation COMObject methods:

So I used my Get-ErrorCode function (available on this page) and got the expected result 🙂

(New-Object -ComObject WSMAN.Automation).GetErrorMessage(            
(Get-ErrorCode 0x80338104).Int64)

Backup Hyper-V with Powershell

A few days ago, Ben Armstrong, the Virtual PC Guy posted on his blog the following:

Why would you backup VMs / When / What’s the best practice
Why? Just in case of a disaster, you should be able to recover your VMs.
When? The best practive analyzer has the answer:

Get-BpaModel  -ModelId Microsoft/Windows/Hyper-V | Invoke-BpaModel

Get-BpaModel | ? Name -match "Hyper-V" |            
Get-BpaResult | ?{($_.Resolution) -and ($_.Severity -eq "Error")}|            
fl -p ResultNumber,Severity,Category,Title,Problem,Resolution

How do I perform the backup with PowerShell

  • Prerequisite #1: Install the backup features
  • Get-WindowsFeature | ? { $_.DisplayName -match "Backup" }

    Add-WindowsFeature -Name Windows-Server-Backup -IncludeAllSubFeature:$true -Restart:$false

  • Prerequisite #2: Make sure the integration services have been installed
  • Get-VM | ? State -eq "Running" |            
    Select Name,State,Integrationservicesversion |            
    ft -AutoSize

    Query the VM integration services version of running VMs to prevent them going into a saved state if they don’t have the latest VM integration services installed.
    Ben Armstrong, the Virtual PC Guy in his posts also said:

    The virtual machine will only be put into a saved state if it is not running the latest virtual machine additions (or is not a Windows virtual machine).

    More info about VM integration services, see my post: https://p0w3rsh3ll.wordpress.com/2012/11/15/follow-up-about-hyper-v-vm-integration-services/

  • Check what VM can be backed up and how
  • Get-WBVirtualMachine  | ft VMName,Caption -AutoSize


    If the VM is off, the backup is performed from the saved state.
    If the VM is running, the backup is performed using a snapshot.

  • Backup a single VM manually and synchronously
  • # Create an empty policy object                        
    $pol = New-WBPolicy            
    

    # Add a single VM to the backup            
    Get-WBVirtualMachine | ? VMName -eq myVMName | Add-WBVirtualMachine -Policy $pol

    # Define a target folder for the backup            
    $targetvol = New-WBBackupTarget -NetworkPath \\mytargetserver.fqdn\HVBackup$ -Credential (Get-Credential) -NonInheritAcl:$false -Verbose            
    Add-WBBackupTarget -Policy $pol -Target $targetvol

    # Define a schedule            
    Set-WBSchedule -Policy $pol -Schedule ([datetime]::Now.AddMinutes(10))            
                
    # Start the backup synchronously            
    Start-WBBackup -Policy $pol


    Note that you cannot start a saved VM (off) when there’s a backup in progress:

    # Review the result            
    Get-WBSummary            
    Get-WBBackupSet -BackupTarget (Get-WBBackupTarget $pol)            
    Get-WBJob -Previous 1


We have the ability to add multiple VMs to a single backup job. However VMs will be backed up sequentially.
You may have also seen how tedious it can be to perform the recovery in Ben Armstrong’s blog post, so I propose the following code snippet that may help:

$BackupShare = "\\mytargetserver.fqdn\HVBackup$"
$cred = (Get-Credential)
Get-VM | ForEach-Object -Process {
    $targetfolder = Join-Path -Path $BackupShare -ChildPath "$($env:COMPUTERNAME)\$($_.Name)"
    If (Test-Path $targetfolder) {
        Write-Verbose -Message "Previous backup for VM $($_.Name) found" -Verbose
        # Delete it first
        try {
            Remove-Item -Path $targetfolder -Force -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to remove target folder $targetfolder"
            return
        }
        # Create directory
        try {
            New-Item -Path $targetfolder -ItemType Directory -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to create target folder $targetfolder"
            return
        }
    } else {
        # Create directory
        try {
            New-Item -Path $targetfolder -ItemType Directory -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to create target folder $targetfolder"
            return
        }
    }

    # Create an empty policy object            
    $pol = New-WBPolicy

    # Add the VM
    Get-WBVirtualMachine | ? VMName -eq $_.Name | Add-WBVirtualMachine -Policy $pol
    
    # Define a target folder
    $targetvol = New-WBBackupTarget -NetworkPath $targetfolder -Credential $cred -NonInheritAcl:$false -Verbose
    Add-WBBackupTarget -Policy $pol -Target $targetvol

    # Set a schedule
    Set-WBSchedule -Policy $pol -Schedule ([datetime]::Now.AddMinutes(10))

    # Start the backup
    Start-WBBackup -Policy $pol -Async 

    # Let us know the status of the running job
    While((Get-WBJob).JobState -eq "Running") { 
        $percent = ([int](@(([regex]'.*\((?<percent>\d{2})\%\).*').Matches((Get-WBJob).CurrentOperation).Groups)[-1].Value))
        if ($percent) {
            Write-Progress -Activity "Backup of VM $($_.Name) in progress" -Status "Percent completed: " -PercentComplete $percent
        } else {
            Write-Progress -Activity "Backup of VM $($_.Name) in progress" -Status (Get-WBJob).CurrentOperation
        }
    }
}