June 28, 2023

BGINFO - A Posh Recreation

Recently I have been building a lot of Windows Servers in different environments - one of the things I used to love to install as a system administrator was BGINFO. This helped me keep track of which server I was on. BGINFO was a stable tool for any system I built and administered for quite a while

https://learn.microsoft.com/en-us/sysinternals/downloads/bginfo

It would, as you expect, put information on the desktop image - this was very useful - made me less prone to mistakes. Mistakes make GrumpyAdmin very grumpy, and in my company, if people make a stupid mistake - they are Pizza Fined, which means they have to buy Pizza for the team!  There is a reason; I am poor, overweight and looking at needing a heart bypass in the near future!!!

BGINFO helped ground an administrator into which system/server they were on!

Legacy BGINFO 

However, BGINFO, an attack vector for particular environments, was banned and removed. MS has resolved the issues and is now safe and part of the Powertoys from Microsoft - However, it has now been delisted in most workplaces and environments I deal with.

https://www.securitynewspaper.com/2017/05/19/bypassing-application-whitelisting-bginfo/

Naturally, many replacements can be used - but the grumpy admin like to do his own thing and stuff and likes Powershell, and he thought to himself - can I do this in Powershell?

As I get easily distracted at work, I created a BGINFO PowerShell-type script.

I thought about it and the logical steps that I needed to take:

Get the information into variables - what variables would I want to get?, well things I feel are essential to know at a glance on a server is:

Hostname
The Operating System
The processor
Total Memory in GB
The IP address
System UPTIME
The domain name
The current logon user
Windows Defender last updated

Now as a PowerShell guru, we know how to collect these things.

Variables

Wow that works - So now I have this - I need a way for PowerShell to modify the desktop background.

This is where we can look at and use the C# abilities of Powershell.

https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics?view=windowsdesktop-7.0

So as you can see, there are lots of capabilities to manipulate graphics - looking at the table of classes - there is something we can use to achieve our aim!

DRAWSTRING

Now that I know that, I can start to create a function that will open an image file and then write the strings from the variables on the image.

After some trial and error work, I developed the following code.

$font = "Arial"
$Size = 16
$OutputImagePath = "C:\GrumpyAdmin\new_background.jpg"
$BackGroundImagePath = "C:\GrumpyAdmin\background.jpg"

# Create a new Graphics object
$image = [System.Drawing.Image]::FromFile($BackgroundImagePath)
$graphic = [System.Drawing.Graphics]::FromImage($image)

$positionX = 10
$positionY = 10
  
# Draw the text
$position = New-Object System.Drawing.PointF($positionX, $positionY)
$graphic.DrawString($MachineName, $font, $brush, $position)

# Save the modified image
$image.Save($OutputImagePath)



Powershell Example Code

Wow, that works! Amazing!

New background image file created

This proves the concept will work! So the next step is to get the code to write every variable on the desktop image!

Typically when dealing with Powershell and variables - it is much better to create what they call a HashMap

So here is a simple HashMap for my variables

$variables = @{
    "Machine Name" = $machineName
    "Operating System" = $operatingSystem
    "Processor" = $processor
    "Total Memory (GB)" = "{0:N2}" -f $totalMemory
    "IP Address" = $ipAddress
    "Uptime" = $uptime
    "Defender Last Updated" = $defenderLastUpdated
    "Domain Name" = $domainName
    "Current Logon User" = $currentLogonUser
    }



My Hash Map

Then we need to loop through the Hashmap and put them on JPG image.

Turn the String in to something readable

So this part of the problem is sorted - we can generate a JPG image with our variable information printed on it!

The next thing to do is to set that as the active background image.

Much Nicer Output :) 

Here is a quick Google, and I find some sample code that does the trick - why reinvent the wheel? Grumpy Admin does something very common: Swipping with Pride - I am not the smartest of the best in the world, why trouble myself with writing and figuring stuff out when you can Google and swipe with pride? :)

The main thing is that as long as you understand what the code you steal does and that you don't violate any licences, you are golden!

# Set the wallpaper path in the registry
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name Wallpaper -Value $OutputImagePath

# Set the wallpaper style in the registry
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -Value 2

# Refresh the desktop
$user32Dll = Add-Type -MemberDefinition @"[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); "@ -Name "User32Dll" -Namespace "User32" -PassThru

$SPI_SETDESKWALLPAPER = 20
$SPIF_UPDATEINIFILE = 0x01
$SPIF_SENDCHANGE = 0x02
$result = $user32Dll::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $OutputImagePath, $SPIF_UPDATEINIFILE -bor $SPIF_SENDCHANGE)
Swipped Code with Pride 

Combine the whole thing, and I have a working prototype PowerShell code! Yippy!

Working Prototype Code

Now as always, I have a working code example. It is time to turn that into a total production standard code!

There are many function templates, and the standard is well documented - so if you are doing POSH code much, you should know this.
I am going to call this function - using the standard verb method.

Add-TextToImage

So variables are extracted, and a CmdLetBinding is created for each.

I used If statements used to toggle elements of the code on or off
Added comments to the code at various structural points

Here is the completed  working function - Enjoy

function Add-TextToImage {
    <#
    .SYNOPSIS
    Adds text to an image using machine information variables. Written by Grumpy Admin Version 0.01 28/06/2023
    
    .DESCRIPTION
    This function takes a background image and adds text to it using machine information variables, similar to the BGInfo application. The modified image is saved as a JPG.
    
    .PARAMETER BackgroundImagePath
    The path to the background image.
    
    .PARAMETER OutputImagePath
    The path to save the modified image.
    
    .PARAMETER Font
    The font to be used for the text. Default: "Arial".
    
    .PARAMETER Size
    The font size. Default: 14.
    
    .PARAMETER AntiAlias
    Specifies whether to enable anti-aliasing for the text. Default: $true.
    
    
    .PARAMETER SetAsDesktopBackground
    Specifies whether to set the modified image as the active desktop background. Default: $false.
    
    .EXAMPLE
    Add-TextToImage -BackgroundImagePath "C:\path\to\background.jpg" -OutputImagePath "C:\path\to\output.jpg" -Size 16 -AntiAlias $false  -SetAsDesktopBackground
    
    Adds text to the background image using machine information variables with a font size of 16, no anti-aliasing. Sets the modified image as the active desktop background.
    #>
    
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Path to the background image.")]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [String]$BackgroundImagePath,

        [Parameter(Mandatory = $true, Position = 1, HelpMessage = "Path to save the modified image.")]
        [String]$OutputImagePath,

        [Parameter(Position = 2, HelpMessage = "The font to be used for the text.")]
        [String]$FontName = "Arial",

        [Parameter(Position = 3, HelpMessage = "The font size.")]
        [int]$Size = 16,

        [Parameter(Position = 4, HelpMessage = "Specifies whether to enable anti-aliasing for the text.")]
        [bool]$AntiAlias = $true,

        [Parameter(Position = 5, HelpMessage = "Specifies whether to set the modified image as the active desktop background.")]
        [switch]$SetAsDesktopBackground
    )
    
    
    # Get machine information
    $machineName = $env:COMPUTERNAME
    $operatingSystem = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
    $processor = (Get-CimInstance -ClassName Win32_Processor).Name
    $totalMemory = (Get-CimInstance -ClassName Win32_ComputerSystem).TotalPhysicalMemory / 1GB
    $ipAddress = (Get-NetIPAddress | Where-Object { $_.AddressFamily -eq 'IPv4' -and $_.InterfaceAlias -ne 'Loopback' }).IPAddress





    # Additional machine information variables
    $LastBootUpTime = (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime
    $SystemUpTime = (Get-Date) - $LastBootUpTime

    $Days = $SystemUpTime.Days
    $Hours = $SystemUpTime.Hours
    $Minutes = $SystemUpTime.Minutes
    $Seconds = $SystemUpTime.Seconds
    
    $uptime = "$Days days, $Hours hours, $Minutes minutes"
    
    
    
    
    $defenderLastUpdated = (Get-MpComputerStatus).AntivirusSignatureLastUpdated 
    $domainName = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain 
    $currentLogonUser = $env:USERNAME

    # Create a hashtable to store the variable names and their corresponding values
    $variables = @{
    "Machine Name" = $machineName
    "Operating System" = $operatingSystem
    "Processor" = $processor
    "Total Memory (GB)" = "{0:N2}" -f $totalMemory
    "IP Address" = $ipAddress
    "Uptime" = $uptime
    "Defender Last Updated" = $defenderLastUpdated
    "Domain Name" = $domainName
    "Current Logon User" = $currentLogonUser
    }

  
    # Create a new Graphics object
    $image = [System.Drawing.Image]::FromFile($BackgroundImagePath)
    $graphic = [System.Drawing.Graphics]::FromImage($image)

    # Set the font properties
    $font = New-Object System.Drawing.Font($FontName, $Size, [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
    $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)

    # Set anti-aliasing
    if ($AntiAlias) {
        $graphic.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
    }
   
    $positionX = 10
    $positionY = 10

    foreach ($variable in $variables.GetEnumerator()) {
        $variableName = $variable.Key
        $variableValue = $variable.Value

        # Draw the text
        $position = New-Object System.Drawing.PointF($positionX, $positionY)
        $graphic.DrawString($variableName +":" + $variableValue, $font, $brush, $position)

        # Increase the Y position for the next variable
        $positionY += 20
    }

    # Save the modified image
    $image.Save($OutputImagePath)

    # Clean up
    $graphic.Dispose()
    $font.Dispose()
    $brush.Dispose()

    # Set the wallpaper path in the registry
        Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name Wallpaper -Value $OutputImagePath

        # Set the wallpaper style in the registry
        Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -Value 2

        # Refresh the desktop
        $user32Dll = Add-Type -MemberDefinition @"
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
"@ -Name "User32Dll" -Namespace "User32" -PassThru

        $SPI_SETDESKWALLPAPER = 20
        $SPIF_UPDATEINIFILE = 0x01
        $SPIF_SENDCHANGE = 0x02
        $result = $user32Dll::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $OutputImagePath, $SPIF_UPDATEINIFILE -bor $SPIF_SENDCHANGE)

        }
Swipped Code with Pride