Skip to content

Instantly share code, notes, and snippets.

@dieseltravis
Last active April 23, 2024 15:16
Show Gist options
  • Save dieseltravis/3066def0ddaf7a8a0b6d to your computer and use it in GitHub Desktop.
Save dieseltravis/3066def0ddaf7a8a0b6d to your computer and use it in GitHub Desktop.
update wallpaper background image with powershell (like Sysinternals BGInfo)
# PS-BGInfo
# Powershell script that updates the background image with a random image from a folder and writes out system info text to it.
# run as a lower priority task
[System.Threading.Thread]::CurrentThread.Priority = 'BelowNormal'
# Configuration:
# Font Family name
$font="Input"
# Font size in pixels
$size=10.0
# spacing in pixels
$textPaddingLeft = 10
$textPaddingTop = 10
$textItemSpace = 3
$wallpaperImagesSource = "$Env:USERPROFILE\Pictures\wallpaper"
$wallpaperImageOutput = "$Env:USERPROFILE"
# Get local info to write out to wallpaper
$os = Get-CimInstance Win32_OperatingSystem
$release = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
$cpu = (Get-WmiObject Win32_Processor).Name.Replace("Intel(R) Core(TM) ", "")
$BootTimeSpan = (New-TimeSpan -Start $os.LastBootUpTime -End (Get-Date))
# get external IP address
$external = (Invoke-WebRequest -UseBasicParsing "ifconfig.me/ip").Content.Trim()
# get array of internal IPs
$ip = (Get-NetIPAddress | Where-Object {$_.InterfaceAlias -eq "Ethernet" -and $_.AddressFamily -eq "IPv4"}).IPAddress
$o = ([ordered]@{
User = $os.RegisteredUser
Host = "$($os.CSName) `n$($os.Description)"
CPU = $cpu
RAM = "$([math]::round($os.TotalVisibleMemorySize / 1MB))GB"
OS = "$($os.Caption) `n$($os.OSArchitecture), $($os.Version), $release"
Boot = $os.LastBootUpTime
Uptime = "$($BootTimeSpan.Days) days, $($BootTimeSpan.Hours) hours"
Snapshot = $os.LocalDateTime
External = $external
})
# loop through each IP address and add it to the object
$ipIndex = 1
$ips | ForEach {
$o["IP" + $ipIndex] = $_
$ipIndex++
}
# original src: https://p0w3rsh3ll.wordpress.com/2014/08/29/poc-tatoo-the-background-of-your-virtual-machines/
Function New-ImageInfo {
# src: https://github.com/fabriceleal/Imagify/blob/master/imagify.ps1
param(
[Parameter(Mandatory=$True, Position=1)]
[object] $data,
[Parameter(Mandatory=$True)]
[string] $in,
[string] $font="Courier New",
[float] $size=12.0,
[float] $textPaddingLeft = 0,
[float] $textPaddingTop = 0,
[float] $textItemSpace = 0,
[string] $out="out.png"
)
[system.reflection.assembly]::loadWithPartialName('system') | out-null
[system.reflection.assembly]::loadWithPartialName('system.drawing') | out-null
[system.reflection.assembly]::loadWithPartialName('system.drawing.imaging') | out-null
[system.reflection.assembly]::loadWithPartialName('system.windows.forms') | out-null
$foreBrush = [System.Drawing.Brushes]::White
$backBrush = new-object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(192, 0, 0, 0))
# Create Bitmap
$SR = [System.Windows.Forms.Screen]::AllScreens | Where-Object Primary | Select-Object -ExpandProperty Bounds | Select-Object Width,Height
Write-Output $SR >> "$wallpaperImageOutput\wallpaper.log"
$background = new-object system.drawing.bitmap($SR.Width, $SR.Height)
$bmp = new-object system.drawing.bitmap -ArgumentList $in
# Create Graphics
$image = [System.Drawing.Graphics]::FromImage($background)
# Paint image's background
$rect = new-object system.drawing.rectanglef(0, 0, $SR.width, $SR.height)
$image.FillRectangle($backBrush, $rect)
# add in image
$topLeft = new-object System.Drawing.RectangleF(0, 0, $SR.Width, $SR.Height)
$image.DrawImage($bmp, $topLeft)
# Draw string
$strFrmt = new-object system.drawing.stringformat
$strFrmt.Alignment = [system.drawing.StringAlignment]::Near
$strFrmt.LineAlignment = [system.drawing.StringAlignment]::Near
$taskbar = [System.Windows.Forms.Screen]::AllScreens
$taskbarOffset = $taskbar.Bounds.Height - $taskbar.WorkingArea.Height
# first get max key & val widths
$maxKeyWidth = 0
$maxValWidth = 0
$textBgHeight = 0 + $taskbarOffset
$textBgWidth = 0
# a reversed ordered collection is used since it starts from the bottom
$reversed = [ordered]@{}
foreach ($h in $data.GetEnumerator()) {
$valString = "$($h.Value)"
$valFont = New-Object System.Drawing.Font($font, $size, [System.Drawing.FontStyle]::Regular)
$valSize = [system.windows.forms.textrenderer]::MeasureText($valString, $valFont)
$maxValWidth = [math]::Max($maxValWidth, $valSize.Width)
$keyString = "$($h.Name): "
$keyFont = New-Object System.Drawing.Font($font, $size, [System.Drawing.FontStyle]::Bold)
$keySize = [system.windows.forms.textrenderer]::MeasureText($keyString, $keyFont)
$maxKeyWidth = [math]::Max($maxKeyWidth, $keySize.Width)
$maxItemHeight = [math]::Max($valSize.Height, $keySize.Height)
$textBgHeight += ($maxItemHeight + $textItemSpace)
$reversed.Insert(0, $h.Name, $h.Value)
}
$textBgWidth = $maxKeyWidth + $maxValWidth + $textPaddingLeft
$textBgHeight += $textPaddingTop
$textBgX = $SR.Width - $textBgWidth
$textBgY = $SR.Height - $textBgHeight
$textBgRect = New-Object System.Drawing.RectangleF($textBgX, $textBgY, $textBgWidth, $textBgHeight)
$image.FillRectangle($backBrush, $textBgRect)
Write-Output $textBgRect >> "$wallpaperImageOutput\wallpaper.log"
$i = 0
$cumulativeHeight = $SR.Height - $taskbarOffset
foreach ($h in $reversed.GetEnumerator()) {
$valString = "$($h.Value)"
$valFont = New-Object System.Drawing.Font($font, $size, [System.Drawing.FontStyle]::Regular)
$valSize = [system.windows.forms.textrenderer]::MeasureText($valString, $valFont)
$keyString = "$($h.Name): "
$keyFont = New-Object System.Drawing.Font($font, $size, [System.Drawing.FontStyle]::Bold)
$keySize = [system.windows.forms.textrenderer]::MeasureText($keyString, $keyFont)
Write-Output $valString >> "$wallpaperImageOutput\wallpaper.log"
Write-Output $keyString >> "$wallpaperImageOutput\wallpaper.log"
$maxItemHeight = [math]::Max($valSize.Height, $keySize.Height) + $textItemSpace
$valX = $SR.Width - $maxValWidth
$valY = $cumulativeHeight - $maxItemHeight
$keyX = $valX - $maxKeyWidth
$keyY = $valY
$valRect = New-Object System.Drawing.RectangleF($valX, $valY, $maxValWidth, $valSize.Height)
$keyRect = New-Object System.Drawing.RectangleF($keyX, $keyY, $maxKeyWidth, $keySize.Height)
$cumulativeHeight = $valRect.Top
$image.DrawString($keyString, $keyFont, $foreBrush, $keyRect, $strFrmt)
$image.DrawString($valString, $valFont, $foreBrush, $valRect, $strFrmt)
$i++
}
# Close Graphics
$image.Dispose();
# Save and close Bitmap
$background.Save($out, [system.drawing.imaging.imageformat]::Png);
$background.Dispose();
$bmp.Dispose();
# Output file
Get-Item -Path $out
}
#TODO: there in't a better way to do this than inline C#?
Add-Type @"
using System.Runtime.InteropServices;
namespace Wallpaper
{
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 UpdateWallpaper (string path)
{
SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange );
}
}
}
"@
Function Set-Wallpaper {
# original src: http://powershell.com/cs/blogs/tips/archive/2014/01/10/change-desktop-wallpaper.aspx
param(
[Parameter(Mandatory=$true)]
$Path,
[ValidateSet('Center', 'Stretch', 'Fill', 'Tile', 'Fit')]
$Style = 'Center'
)
# this is likely to be the same every time
Set-ItemProperty -Path "HKCU:Control Panel\Desktop" -Name WallPaper -Value $Path
$ws = 0
$tw = 0
switch ( $Style )
{
'Center' { $ws = 0; $tw = 0; }
'Stretch' { $ws = 2; $tw = 0; }
'Fill' { $ws = 10; $tw = 0; }
'Tile' { $ws = 0; $tw = 1; }
'Fit' { $ws = 6; $tw = 0; }
}
Set-ItemProperty -Path "HKCU:Control Panel\Desktop" -Name WallpaperStyle -Value $ws
Set-ItemProperty -Path "HKCU:Control Panel\Desktop" -Name TileWallpaper -Value $tw
# wait 5 seconds
Start-Sleep -s 5
[Wallpaper.Setter]::UpdateWallpaper( $Path )
# alternate (I can't get these to work):
#& RUNDLL32.EXE user32.dll, UpdatePerUserSystemParameters, 1, True
#& RUNDLL32.EXE user32.dll, SystemParametersInfo, 20, 0, $Path, 3
#RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters 1, True
}
# execute tasks
Write-Output $o > "$wallpaperImageOutput\wallpaper.log"
# get random wallpaper from a folder full of images
Get-ChildItem -Path "$wallpaperImagesSource\*" -Include *.* -Exclude current.jpg | Get-Random | Foreach-Object { Copy-Item -Path $_ -Destination "$wallpaperImagesSource\current.jpg" }
# create wallpaper image and save it in user profile
$WallPaper = New-ImageInfo -data $o -in "$wallpaperImagesSource\current.jpg" -out "$wallpaperImageOutput\wallpaper.png" -font $font -size $size -textPaddingLeft $textPaddingLeft -textPaddingTop $textPaddingTop -textItemSpace $textItemSpace
Write-Output $WallPaper.FullName >> "$wallpaperImageOutput\wallpaper.log"
# update wallpaper for logged in user
Set-Wallpaper -Path $WallPaper.FullName
' Win32_ProcessStartup info: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processstartup
' PriorityClass values
Const LOW = 64 ' Indicates a process with threads that run only when the system is idle and are preempted by the threads of any process running in a higher priority class. An example is a screen saver. The idle priority class is inherited by child processes.
Const BELOW_NORMAL = 16384 ' Indicates a process that has a priority higher than Idle but lower than Normal.
Const NORMAL = 32 ' Indicates a normal process with no special schedule needs.
Const ABOVE_NORMAL = 32768 ' Indicates a process that has a priority higher than Normal but lower than High
Const HIGH = 128 ' Indicates a process that performs time-critical tasks that must be executed immediately to run correctly. The threads of a high-priority class process preempt the threads of normal-priority or idle-priority class processes. An example is Windows Task List, which must respond quickly when called by the user, regardless of the load on the operating system. Use extreme care when using the high-priority class, because a high-priority class CPU-bound application can use nearly all of the available cycles. Only a real-time priority preempts threads set to this level.
Const REALTIME = 256 ' Indicates a process that has the highest possible priority. The threads of a real-time priority class process preempt the threads of all other processes—including high-priority threads and operating system processes performing important tasks. For example, a real-time process that executes for more than a very brief interval can cause disk caches not to flush, or cause a mouse to be unresponsive.
' ShowWindow values
Const SW_HIDE = 0 ' Hides the window and activates another window.
Const SW_NORMAL = 1 ' Activates and displays a window. If the window is minimized or maximized, the system restores it to the original size and position. An application specifies this flag when displaying the window for the first time.
Const SW_SHOWMINIMIZED = 2 ' Activates the window, and displays it as a minimized window.
Const SW_SHOWMAXIMIZED = 3 ' Activates the window, and displays it as a maximized window.
Const SW_SHOWNOACTIVATE = 4 ' Displays a window in its most recent size and position. This value is similar to SW_NORMAL, except that the window is not activated.
Const SW_SHOW = 5 ' Activates the window, and displays it at the current size and position.
Const SW_MINIMIZE = 6 ' Minimizes the specified window, and activates the next top-level window in the Z order.
Const SW_SHOWMINNOACTIVE = 7 ' Displays the window as a minimized window. This value is similar to SW_SHOWMINIMZED, except that the window is not activated.
Const SW_SHOWNA = 8 ' Displays the window at the current size and position. This value is similar to SW_SHOW, except that the window is not activated.
Const SW_RESTORE = 9 ' Activates and displays the window. If the window is minimized or maximized, the system restores it to the original size and position. An application specifies this flag when restoring a minimized window.
Const SW_SHOWDEFAULT = 10 ' Sets the show state based on the SW_* value that is specified in the STARTUPINFO structure passed to the CreateProcess function by the program that starts the application.
Const SW_FORCEMINIMIZE = 11 ' Minimizes a window, even when the thread that owns the window stops responding. Only use this flag when minimizing windows from a different thread.
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
objConfig.PriorityClass = BELOW_NORMAL
objConfig.ShowWindow = SW_HIDE
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
objProcess.Create "powershell.exe -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File C:\utils\PS-BGInfo.ps1", Null, objConfig, intProcessID
@dimiboy
Copy link

dimiboy commented Sep 7, 2020

I'm having an issue when applying it on a non standard resolution image, for example a smaller photo, the result is that it stretches the image and than applies the text overlay and I didn't find how to keep the original image size.
Please assist

@dieseltravis
Copy link
Author

@dimiboy It looks like the code resizes the image to the screen resolution a few places in the New-ImageInfo function starting here:
https://gist.github.com/dieseltravis/3066def0ddaf7a8a0b6d#file-ps-bginfo-ps1-L69

The easiest workaround would probably be to just redefine the $SR object to your desired dimensions:
https://gist.github.com/dieseltravis/3066def0ddaf7a8a0b6d#file-ps-bginfo-ps1-L65

@dimiboy
Copy link

dimiboy commented Sep 9, 2020

@dimiboy It looks like the code resizes the image to the screen resolution a few places in the New-ImageInfo function starting here:
https://gist.github.com/dieseltravis/3066def0ddaf7a8a0b6d#file-ps-bginfo-ps1-L69

The easiest workaround would probably be to just redefine the $SR object to your desired dimensions:
https://gist.github.com/dieseltravis/3066def0ddaf7a8a0b6d#file-ps-bginfo-ps1-L65

The thing is I'm running it on multiple PC's and users might set any size of image, I guess I need to find a way to get the size of the current wallpaper and than use same size instead of the screen size for the "New-ImageInfo"

UPDATE:
It worked!! Thank you!!!
What I did is basically dropped the image size into the $SR, something like this:
Add-Type -AssemblyName System.Drawing
$imageSize = New-Object System.Drawing.Bitmap $wallpaperImagesSource\current.jpg
I also mantain the wallpaper style now and everything so if a user decided he want a small photo as center, it will stay as center and the added text box will be on it and not on the screen which on one way might not sound good but on the other the user still can change the style of it in any time and it won't break :-)

@PaddyPat
Copy link

PaddyPat commented Feb 13, 2021

Thank for your script! Could you pls give me a hint, howto to set it on top right corner?

@dieseltravis
Copy link
Author

@PaddyPat well, the way it works now is pretty hard-coded to bottom right, unfortunately.

On line #120 set $textBgY to 0. That would start the text at the top. Then on #130 I am looping through the array that stores the text in reverse for writing the text at the bottom and going up, so since you'll want to start at the top, the easiest way would probably be to reverse the reversed array of text, and that should get you pretty close.

@Aabayoumy
Copy link

@PaddyPat well, the way it works now is pretty hard-coded to bottom right, unfortunately.

On line #120 set $textBgY to 0. That would start the text at the top. Then on #130 I am looping through the array that stores the text in reverse for writing the text at the bottom and going up, so since you'll want to start at the top, the easiest way would probably be to reverse the reversed array of text, and that should get you pretty close.

I try to go throw that , the background rectangle is moved to top but not text
can you make the location as variable , it's will be very helpful

@red9r
Copy link

red9r commented Apr 14, 2022

I know this is a long shot but Is there any way to modify the C# code so that the wallpaper is set for all currently logged in users?

@maphew
Copy link

maphew commented Apr 27, 2022

I learned today that Windows Taskbar can have a background image (a.k.a. wallpaper) in it:
https://www.addictivetips.com/windows-tips/add-background-image-to-the-taskbar-windows-10/

It would be nice to update this script or an additional similar one to add the machine name to the taskbar. This way even with windows in full screen in a Remote Desktop session peeps (me!) can know at a glance what machine they're on, and slightly lessen the chance of doing The Wrong Thing(tm).

@ninjoan
Copy link

ninjoan commented Jun 10, 2022

hello i need any extra software?
i copy the file PS-BGInfo.ps1 on the local PC and I run using the Powershell ISE but nothing happens

@dieseltravis
Copy link
Author

dieseltravis commented Jun 12, 2022

@Aabayoumy

@PaddyPat well, the way it works now is pretty hard-coded to bottom right, unfortunately.
On line #120 set $textBgY to 0. That would start the text at the top. Then on #130 I am looping through the array that stores the text in reverse for writing the text at the bottom and going up, so since you'll want to start at the top, the easiest way would probably be to reverse the reversed array of text, and that should get you pretty close.

I try to go throw that , the background rectangle is moved to top but not text can you make the location as variable , it's will be very helpful

I'll have to look into this.

@red9r

I know this is a long shot but Is there any way to modify the C# code so that the wallpaper is set for all currently logged in users?

I'm not sure if you'd need to loop each user or if there's a global way to do it.

@maphew

I learned today that Windows Taskbar can have a background image (a.k.a. wallpaper) in it: https://www.addictivetips.com/windows-tips/add-background-image-to-the-taskbar-windows-10/

It would be nice to update this script or an additional similar one to add the machine name to the taskbar. This way even with windows in full screen in a Remote Desktop session peeps (me!) can know at a glance what machine they're on, and slightly lessen the chance of doing The Wrong Thing(tm).

That sounds neat, I'll look into it if I have time.

@ninjoan

hello i need any extra software? i copy the file PS-BGInfo.ps1 on the local PC and I run using the Powershell ISE but nothing happens

You shouldn't need anything else that I can think of. Are there any error messages?

@red9r
Copy link

red9r commented Jun 12, 2022

@dieseltravis, I was able to make it work but one thing that I’m still trying to figure out is how to set the wallpaper for each monitor individually. This would be useful when you have one monitor in landscape and one on portrait mode.

@Kishoshree
Copy link

@PaddyPat well, the way it works now is pretty hard-coded to bottom right, unfortunately.
On line #120 set $textBgY to 0. That would start the text at the top. Then on #130 I am looping through the array that stores the text in reverse for writing the text at the bottom and going up, so since you'll want to start at the top, the easiest way would probably be to reverse the reversed array of text, and that should get you pretty close.

I have set $textBgY to 0, the background rectangle is moved to top but not text. And i have changed On line #128 set '$cumulativeHeight'= $textBgHeight Then text is moved to rectangle on top.

I tried to make the array reversed but still writing the text at the bottom and going up.

@navaizrael
Copy link

Hello, If I wanted to move the info box to the top right how would I do that? Thanks.

@cheloveshka
Copy link

Hello. I think you need to add an ip address change event tracking loop.

@dieseltravis
Copy link
Author

@red9r multi-monitors is one thing that I've never been able to figure out

@Kishoshree & @navaizrael if I ever get that working on my end I'll be sure to update it here

@cheloveshka I just run this ever 15 minutes or so to change my background and update the info

@ everyone else I added a line to record the External IP address and added updates to loop through the internal IP addresses.

@cquiroz2021
Copy link

I'm new with PowerShell, so HELP! How do I stop this process on my machine and remove it? I did it too fast without editing the text.

@nthoman
Copy link

nthoman commented Apr 23, 2024

Worked like a charm. Thank you

2019-10-02_15-29-40

Where did you end up putting the logo code? Mine isn't showing up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment