PoC: Tatoo the background of your virtual machines

You may know the popular Bginfo from Sysinternals and that even Azure uses this utility to tatoo the background of virtual machines.

I wondered if PowerShell (alone) would make it and avoid the dependency on an external binary.

I started to use Google and finally decided to fork the following code available on github: https://github.com/fabriceleal/Imagify/blob/master/imagify.ps1

I also needed to find the way to set a wallpaper under Windows 7 and later…
I decided to extend this PowerTip: http://powershell.com/cs/blogs/tips/archive/2014/01/10/change-desktop-wallpaper.aspx because the rundll32 tricks doesn’t work.

I created two functions, one to create a new background image either from scratch and based on a colored theme (blue, grey and black) or from the existing wallpaper and the second one to set this image as a wallpaper.

Function New-BGinfo {
    Param(  [Parameter(Mandatory)]
            [string] $Text,

            [Parameter()]
            [string] $OutFile= "$($env:temp)\BGInfo.bmp",

            [Parameter()]
            [ValidateSet("Left","Center")]
            [string]$Align="Center",


            [Parameter()]
            [ValidateSet("Blue","Grey","Black")]
            [string]$Theme="Blue",

            [Parameter()]
            [string]$FontName="Arial",

            [Parameter()]
            [ValidateRange(9,45)]
            [int32]$FontSize = 12,

            [Parameter()]
            [switch]$UseCurrentWallpaperAsSource
    )
Begin {

    Switch ($Theme) {
        Blue {
            $BG = @(58,110,165)
            $FC1 = @(254,253,254)
            $FC2 = @(185,190,188)
            $FS1 = $FontSize+1
            $FS2 = $FontSize-2
            break
        }
        Grey {
            $BG = @(77,77,77)
            $FC1 = $FC2 = @(255,255,255)
            $FS1=$FS2=$FontSize
            break
        }
        Black {
            $BG = @(0,0,0)
            $FC1 = $FC2 = @(255,255,255)
            $FS1=$FS2=$FontSize
        }
    }
    Try {
        [system.reflection.assembly]::loadWithPartialName('system.drawing.imaging') | out-null
        [system.reflection.assembly]::loadWithPartialName('system.windows.forms') | out-null

        # Draw string > alignement
        $sFormat = new-object system.drawing.stringformat

        Switch ($Align) {
            Center {
                $sFormat.Alignment = [system.drawing.StringAlignment]::Center
                $sFormat.LineAlignment = [system.drawing.StringAlignment]::Center
                break
            }
            Left {
                $sFormat.Alignment = [system.drawing.StringAlignment]::Center
                $sFormat.LineAlignment = [system.drawing.StringAlignment]::Near
            }
        }


        if ($UseCurrentWallpaperAsSource) {
            $wpath = (Get-ItemProperty 'HKCU:\Control Panel\Desktop' -Name WallPaper -ErrorAction Stop).WallPaper
            if (Test-Path -Path $wpath -PathType Leaf) {
                $bmp = new-object system.drawing.bitmap -ArgumentList $wpath
                $image = [System.Drawing.Graphics]::FromImage($bmp)
                $SR = $bmp | Select Width,Height
            } else {
                Write-Warning -Message "Failed cannot find the current wallpaper $($wpath)"
                break
            }
        } else {
            $SR = [System.Windows.Forms.Screen]::AllScreens | Where Primary | 
            Select -ExpandProperty Bounds | Select Width,Height

            Write-Verbose -Message "Screen resolution is set to $($SR.Width)x$($SR.Height)" -Verbose

            # Create Bitmap
            $bmp = new-object system.drawing.bitmap($SR.Width,$SR.Height)
            $image = [System.Drawing.Graphics]::FromImage($bmp)
    
            $image.FillRectangle(
                (New-Object Drawing.SolidBrush (
                    [System.Drawing.Color]::FromArgb($BG[0],$BG[1],$BG[2])
                )),
                (new-object system.drawing.rectanglef(0,0,($SR.Width),($SR.Height)))
            )

        }
    } Catch {
        Write-Warning -Message "Failed to $($_.Exception.Message)"
        break
    }
}
Process {

    # Split our string as it can be multiline
    $artext = ($text -split "\r\n")
    
    $i = 1
    Try {
        for ($i ; $i -le $artext.Count ; $i++) {
            if ($i -eq 1) {
                $font1 = New-Object System.Drawing.Font($FontName,$FS1,[System.Drawing.FontStyle]::Bold)
                $Brush1 = New-Object Drawing.SolidBrush (
                    [System.Drawing.Color]::FromArgb($FC1[0],$FC1[1],$FC1[2])
                )
                $sz1 = [system.windows.forms.textrenderer]::MeasureText($artext[$i-1], $font1)
                $rect1 = New-Object System.Drawing.RectangleF (0,($sz1.Height),$SR.Width,$SR.Height)
                $image.DrawString($artext[$i-1], $font1, $brush1, $rect1, $sFormat) 
            } else {
                $font2 = New-Object System.Drawing.Font($FontName,$FS2,[System.Drawing.FontStyle]::Bold)
                $Brush2 = New-Object Drawing.SolidBrush (
                    [System.Drawing.Color]::FromArgb($FC2[0],$FC2[1],$FC2[2])
                )
                $sz2 = [system.windows.forms.textrenderer]::MeasureText($artext[$i-1], $font2)
                $rect2 = New-Object System.Drawing.RectangleF (0,($i*$FontSize*2 + $sz2.Height),$SR.Width,$SR.Height)
                $image.DrawString($artext[$i-1], $font2, $brush2, $rect2, $sFormat)
            }
        }
    } Catch {
        Write-Warning -Message "Failed to $($_.Exception.Message)"
        break
    }
}
End {   
    Try { 
        # Close Graphics
        $image.Dispose();

        # Save and close Bitmap
        $bmp.Save($OutFile, [system.drawing.imaging.imageformat]::Bmp);
        $bmp.Dispose();

        # Output our file
        Get-Item -Path $OutFile
    } Catch {
        Write-Warning -Message "Failed to $($_.Exception.Message)"
        break
    }
}

} # endof function

Function Set-Wallpaper {
    Param(
        [Parameter(Mandatory=$true)]
        $Path,
        
        [ValidateSet('Center','Stretch','Fill','Tile','Fit')]
        $Style = 'Stretch'
    )
    Try {
        if (-not ([System.Management.Automation.PSTypeName]'Wallpaper.Setter').Type) {
            Add-Type -TypeDefinition @"
            using System;
            using System.Runtime.InteropServices;
            using Microsoft.Win32;
            namespace Wallpaper {
                public enum Style : int {
                Center, Stretch, Fill, Fit, Tile
                }
                public class Setter {
                    public const int SetDesktopWallpaper = 20;
                    public const int UpdateIniFile = 0x01;
                    public const int SendWinIniChange = 0x02;
                    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
                    private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni);
                    public static void SetWallpaper ( string path, Wallpaper.Style style ) {
                        SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
                        RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);
                        switch( style ) {
                            case Style.Tile :
                                key.SetValue(@"WallpaperStyle", "0") ; 
                                key.SetValue(@"TileWallpaper", "1") ; 
                                break;
                            case Style.Center :
                                key.SetValue(@"WallpaperStyle", "0") ; 
                                key.SetValue(@"TileWallpaper", "0") ; 
                                break;
                            case Style.Stretch :
                                key.SetValue(@"WallpaperStyle", "2") ; 
                                key.SetValue(@"TileWallpaper", "0") ;
                                break;
                            case Style.Fill :
                                key.SetValue(@"WallpaperStyle", "10") ; 
                                key.SetValue(@"TileWallpaper", "0") ; 
                                break;
                            case Style.Fit :
                                key.SetValue(@"WallpaperStyle", "6") ; 
                                key.SetValue(@"TileWallpaper", "0") ; 
                                break;
}
                        key.Close();
                    }
                }
            }
"@ -ErrorAction Stop 
            } else {
                Write-Verbose -Message "Type already loaded" -Verbose
            }
        # } Catch TYPE_ALREADY_EXISTS
        } Catch {
            Write-Warning -Message "Failed because $($_.Exception.Message)"
        }
    
    [Wallpaper.Setter]::SetWallpaper( $Path, $Style )
}

Let’s see these two functions in action.

First define some multiline text to be written in the image.

$os = Get-CimInstance Win32_OperatingSystem
($o = [pscustomobject]@{
    HostName =  $env:COMPUTERNAME
    UserName = '{0}\{1}' -f  $env:USERDOMAIN,$env:USERNAME
    'Operating System' = '{0} Service Pack {1} (build {2})' -f  $os.Caption,
    $os.ServicePackMajorVersion,$os.BuildNumber
}) | ft -AutoSize
$BootTime = (New-TimeSpan -Start $os.LastBootUpTime -End (Get-Date)).ToString()

# $t is the multiline text defined as here-string
$t = @"
$($o.HostName)
Logged on user: $($o.UserName)
$($o.'Operating System')
Uptime: $BootTime
"@
  • Exemple 1: ala Backinfo
  • $WallPaper = New-BGinfo -text $t
    Set-Wallpaper -Path $WallPaper.FullName -Style Center
    

  • Exemple 2: ala Bginfo using the current wallpaper
  • $BGHT = @{
     Text  = $t ;
     Theme = "Black" ;
     FontName = "Verdana" ;
     UseCurrentWallpaperAsSource = $true ;
    }
    $WallPaper = New-BGinfo @BGHT
    Set-Wallpaper -Path $WallPaper.FullName -Style Fill
    
    # Restore the default VM wallpaper
    Set-Wallpaper -Path "C:\Windows\Web\Wallpaper\Windows\img0.jpg" -Style Fill
    

This proof of concept based on just a few hundred lines of PowerShell proves that the dependency on Bginfo could be avoided…

Advertisements

10 thoughts on “PoC: Tatoo the background of your virtual machines

  1. Pingback: Friday Five - September 5, 2014 - The Microsoft MVP Award Program Blog - Site Home - MSDN Blogs

  2. Pingback: Friday Five - September 5th, 2014 - The Microsoft MVP Award Program Blog - Site Home - MSDN Blogs

  3. Pingback: Friday Five – September 5th, 2014 | CmdExec Technology News

  4. Pingback: Friday Five – September 5th, 2014 | Technology News | The Black Gazette

  5. Pingback: Friday Five - September 5th, 2014

  6. Pingback: MVP Top Tips—PoC background virtual machines, Real Estate transactions OneDrive OneNote, PowerShell, Windows Server Software defined storage, PCL explained - Canadian IT Manager's Blog - Site Home - TechNet Blogs

  7. Really like this proof of concept…

    Have made a couple of tweaks to support better automation around detecting whether the wallpaper is actually set, or if the current user has a solid colour instead…

    So, have added “Current” as a $Theme (& validation), which goes and grabs the current background colour, and falls back to the default if it needs to:

    Switch ($Theme) {
    Current {
    $RGB = (Get-ItemProperty ‘HKCU:\Control Panel\Colors’ -ErrorAction Stop).BackGround
    if ($RGB.Length -eq 0) {
    $Theme = “Black”
    } else {
    $BG = $RGB -split ” ”
    $FC1 = $FC2 = @(255,255,255)
    $FS1=$FS2=$FontSize
    break
    }
    }
    Blue { ….

    And have moved the $wpath item to the very top of Begin:

    # Enumerate current wallpaper now, so we can decide whether it’s a solid colour or not
    try {
    $wpath = (Get-ItemProperty ‘HKCU:\Control Panel\Desktop’ -Name WallPaper -ErrorAction Stop).WallPaper
    if ($wpath.Length -eq 0) {
    # Solid colour used
    $UseCurrentWallpaperAsSource = $false
    $Theme = “Current”
    }
    } catch {
    $UseCurrentWallpaperAsSource = $false
    $Theme = “Current”
    }

    Code: http://pastebin.com/Fva47UKT

    • To be able to test the code:
      1. Copy/paste the 2 functions’ code into the script pane of the Windows PowerShell ISE running as Administrator.
      2. Copy/paste the code of 1 of the examples, and press F5 in the ISE to run the script.
      If it prompts to save the file (answer yes) and it will then execute.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s