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
@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