Skip to content

Instantly share code, notes, and snippets.

@joegasper
Forked from dieseltravis/PS-BGInfo.ps1
Created October 31, 2023 01:36
Show Gist options
  • Save joegasper/3ab03b1147bb1135b8aabb09f38f6217 to your computer and use it in GitHub Desktop.
Save joegasper/3ab03b1147bb1135b8aabb09f38f6217 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment