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

1

2

$WallPaper = New-BGinfo -text $t

Set-Wallpaper -Path $WallPaper.FullName -Style Center

  • Exemple 2: ala Bginfo using the current wallpaper

1

2

3

4

5

6

7

8

9

10

11

$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…