Skip to content

Instantly share code, notes, and snippets.

@xdhmoore
Last active March 17, 2023 08:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xdhmoore/fa909e1bc7236d36844b86cf9c1a1074 to your computer and use it in GitHub Desktop.
Save xdhmoore/fa909e1bc7236d36844b86cf9c1a1074 to your computer and use it in GitHub Desktop.
Various POCs for changing windows wallpapers, lockscreen wallpapers, screen orientation, etc. with powershell
# These files are code I wrote years ago to change windows 7 wallpaper, etc. with powershell.
# It's been several years since I've used most of them and they may not have been tested on windows 10 or powershell core.
# In particular the Lockscreen code and PoshWinRT dependency do not work w/o powershell core without modifications.
# They were written only for my use and are thus pretty rough. But...it took me a bit of research to get them working and
# they did work at some point (for varying values of "work"). A lot of the content is copied and adapted from blog posts
# I've found, but which at this point I don't remember the source in order to give any attribution.
# Anyway, maybe someone will find them useful. They are here by request, but without any cleanup or polish.
# A few functions require pre-existing files to exist at particular locations on the file system.
# Use at your own risk!
$pinvokeCode = @"
using System;
using System.Runtime.InteropServices;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE
{
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public int dmPositionX;
public int dmPositionY;
public int dmDisplayOrientation;
public int dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
class NativeMethods
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE devMode, int flags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
public const int DMDO_DEFAULT = 0;
public const int DMDO_90 = 1;
public const int DMDO_180 = 2;
public const int DMDO_270 = 3;
}
public class PrmaryScreenResolution
{
static public int GetOrientation()
{
DEVMODE dm = GetDevMode();
if (0 != NativeMethods.EnumDisplaySettings(null, NativeMethods.ENUM_CURRENT_SETTINGS, ref dm))
{
return dm.dmDisplayOrientation;
} else {
return -1;
}
}
static public string ChangeResolution(int orientation)
{
DEVMODE dm = GetDevMode();
if (0 != NativeMethods.EnumDisplaySettings(null, NativeMethods.ENUM_CURRENT_SETTINGS, ref dm))
{
// swap width and height
int temp = dm.dmPelsHeight;
dm.dmPelsHeight = dm.dmPelsWidth;
dm.dmPelsWidth = temp;
if(orientation < 0) {
// determine new orientation based on the current orientation
switch(dm.dmDisplayOrientation)
{
case NativeMethods.DMDO_DEFAULT:
dm.dmDisplayOrientation = NativeMethods.DMDO_270;
break;
case NativeMethods.DMDO_270:
dm.dmDisplayOrientation = NativeMethods.DMDO_180;
break;
case NativeMethods.DMDO_180:
dm.dmDisplayOrientation = NativeMethods.DMDO_90;
break;
case NativeMethods.DMDO_90:
dm.dmDisplayOrientation = NativeMethods.DMDO_DEFAULT;
break;
default:
// unknown orientation value
// add exception handling here
break;
}
} else {
dm.dmDisplayOrientation = orientation;
}
int iRet = NativeMethods.ChangeDisplaySettings(ref dm, NativeMethods.CDS_TEST);
if (iRet == NativeMethods.DISP_CHANGE_FAILED)
{
return "Unable To Process Your Request. Sorry For This Inconvenience.";
}
else
{
iRet = NativeMethods.ChangeDisplaySettings(ref dm, NativeMethods.CDS_UPDATEREGISTRY);
switch (iRet)
{
case NativeMethods.DISP_CHANGE_SUCCESSFUL:
{
return "Success";
}
case NativeMethods.DISP_CHANGE_RESTART:
{
return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode.";
}
default:
{
return "Failed To Change The Resolution";
}
}
}
}
else
{
return "Failed To Change The Resolution.";
}
}
private static DEVMODE GetDevMode()
{
DEVMODE dm = new DEVMODE();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
"@
Add-Type $pinvokeCode -ErrorAction SilentlyContinue
Function Set-ScreenResolutionAndOrientation($orientation) {
<#
.Synopsis
Sets the Screen Resolution of the primary monitor
.Description
Uses Pinvoke and ChangeDisplaySettings Win32API to make the change
.Example
Set-ScreenResolutionAndOrientation
#>
[Resolution.PrmaryScreenResolution]::ChangeResolution($orientation)
}
Function Get-Orient() {
return [Resolution.PrmaryScreenResolution]::GetOrientation();
}
#Set-ScreenResolutionAndOrientation
Function Set-LeftOrient() {
Set-ScreenResolutionAndOrientation 1
}
Function Set-UpsideDownOrient() {
Set-ScreenResolutionAndOrientation 2
}
Function Set-RightOrient() {
Set-ScreenResolutionAndOrientation 3
}
Function Reset-Orient() {
Set-NormOrient
}
Function Set-NormOrient() {
Set-ScreenResolutionAndOrientation 0
}
$signature = @"
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(int uAction, int uParam, ref int lpvParam, int flags );
"@
$systemParamInfo = Add-Type -memberDefinition $signature -Name ScreenSaver -passThru
Function Set-ScreenSaver($screensaver) {
if (($screensaver ?? "").trim() -ne "") {
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name scrnsave.exe -Value $screensaver
} else {
Remove-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name scrnsave.exe
}
#"C:\Windows\gphoto~1.scr"
}
Function Get-ScreenSaverTimeout
{
[Int32]$value = 0
$systemParamInfo::SystemParametersInfo(14, 0, [REF]$value, 0)
#$($value/60)
$value
}
Function Set-ScreenSaverTimeout
{
Param ([Int32]$value)
$seconds = $value# * 60
[Int32]$nullVar = 0
if (!($systemParamInfo::SystemParametersInfo(15, $seconds, [REF]$nullVar, 2))) {
Write-Error "Failed to set screensaver timeout via SystemParametersInfo()"
return;
}
}
#PoshWinRT.dll comes from here: https://github.com/rkeithhill/PoshWinRT
function Set-LockscreenWallpaper($path) {
$PROJ_DIR="$(Get-Item (Get-item $PSCommandPath).DirectoryName)"
Add-Type -Path $($PROJ_DIR + "\PoshWinRT.dll")
[Windows.Storage.StorageFile,Windows.Storage,ContentType=WindowsRuntime] > $null
$asyncOp = [Windows.Storage.StorageFile]::GetFileFromPathAsync($path)
$typeName = 'PoshWinRT.AsyncOperationWrapper[Windows.Storage.StorageFile]'
$wrapper = new-object $typeName -Arg $asyncOp
$file = $wrapper.AwaitResult()
$null = [Windows.System.UserProfile.LockScreen,Windows.System.UserProfile,ContentType=WindowsRuntime]
$asyncOp = [Windows.System.UserProfile.LockScreen]::SetImageFileAsync($file)
$typeName = 'PoshWinRT.AsyncActionWrapper'
$wrapper = new-object $typeName -Arg $asyncOp
$wrapper.AwaitResult()
}
Function Disable-LockscreenSlideshow() {
Set-ItemProperty -path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lock Screen" -name SlideshowEnabled -value 0
rundll32.exe user32.dll, UpdatePerUserSystemParameters
}
Function Enable-LockscreenSlideshow() {
Set-ItemProperty -path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lock Screen" -name SlideshowEnabled -value 1
rundll32.exe user32.dll, UpdatePerUserSystemParameters
}
<#
Function Set-ScreensaverTimeout($timeout) {
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaveTimeout" -Value "$timeout"
# some internet post said to run this three times and it seems to work
for ($i = 0; $i -le 4; $i++) {
rundll32.exe user32.dll, UpdatePerUserSystemParameters
}
#SPI_SETSCREENSAVETIMEOUT
}
#>
#Taken from http://www.culham.net/powershell/changing-desktop-wallpaper-using-windows-powershell/
Add-Type -ReferencedAssemblies Microsoft.Win32.Registry.dll @"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace Wallpaper {
public enum Style : int {
Tile, Center, Stretch, NoChange, Fit
}
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 lpvPara, 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);
// Copied from https://stackoverflow.com/questions/19989906/how-to-set-wallpaper-style-fill-stretch-according-to-windows-version
// Two registry values are set in the Control Panel\Desktop key.
// TileWallpaper
// 0: The wallpaper picture should not be tiled
// 1: The wallpaper picture should be tiled
// WallpaperStyle
// 0: The image is centered if TileWallpaper=0 or tiled if TileWallpaper=1
// 2: The image is stretched to fill the screen
// 6: The image is resized to fit the screen while maintaining the aspect
// ratio. (Windows 7 and later)
// 10: The image is resized and cropped to fill the screen while
// maintaining the aspect ratio. (Windows 7 and later)
switch(style) {
case Style.Stretch:
key.SetValue(@"WallpaperStyle", "2");
key.SetValue(@"TileWallpaper", "0");
break;
case Style.Center:
key.SetValue(@"WallpaperStyle", "1");
key.SetValue(@"TileWallpaper", "0");
break;
case Style.Tile:
key.SetValue(@"WallpaperStyle", "1");
key.SetValue(@"TileWallpaper", "1");
break;
//TODO test this
case Style.Fit:
key.SetValue(@"WallpaperStyle", "6");
key.SetValue(@"TileWallpaper", "0");
break;
case Style.NoChange:
break;
}
key.Close();
}
}
}
"@
Function Set-WallColor() {
Set-Wallpaper "Color" $null
}
Function Set-WallPic($Path) {
Set-Wallpaper "Single" "$Path"
}
Function Set-WallSlides($IniPath) {
Set-Wallpaper "SlideShow" "$IniPath"
}
Function Set-Wallpaper([string]$Type, [string]$Path) {
#TODO figure out how to create slideshow.ini
#There's a mention of the data struct on your superuser post:
#https://superuser.com/questions/1007379/how-do-i-change-the-wallpaper-slideshow-album-programmatically
$themeFolder="~\AppData\Roaming\Microsoft\Windows\Themes"
$configPath="$themeFolder\slideshow.ini"
$createdImg="$themeFolder\TranscodedWallpaper.jpg"
$imgFitStyle=4
$wallpaperStyle=$imgFitStyle
if ($Type -eq "Single") {
if ("$Path" -and !(Test-Path "$Path")) {
Write-Error "Can't find image file '$Path'"
return
}
if (Test-Path "$createdImg") {
rm -Force "$createdImg"
}
if (Test-Path "$configPath") {
#Remove slideshow.ini
rm -Force $configPath
}
[Wallpaper.Setter]::SetWallpaper("$Path", $wallpaperStyle)
} elseif ($Type -eq "Slideshow") {
if (!(Test-Path $Path)) {
Write-Error "Can't find slideshow.ini config file '$Path'"
return
}
[Wallpaper.Setter]::SetWallpaper("", $wallpaperStyle)
#Copy new slideshow.ini
cp -Force "$Path" "$configPath"
} elseif ($Type -eq "Color") {
#Obviously, this isn't changing the color. Could probably change it above if we really want
#Also, this means changing to color resets style to "Fit". Could
#add a 3rd parameter: style
Set-Wallpaper -Type Single -Path ""
} else {
Write-Error "Unknown Type: '$Type'"
return
}
Start-Job -ScriptBlock {
#TODO there has to be a better way to refresh the desktop
#TODO look at https://superuser.com/questions/398605/how-to-force-windows-desktop-background-to-update-or-refresh
Stop-Process -name explorer
#TODO could do this last part async in a job
sleep 1;
if (!(Get-Process -name explorer)) {
#TODO is there a way to get back the focus after this?
Start-Process explorer
}
} | Out-Null
}
Function Next-Slide() {
$shellApp = New-Object -ComObject Shell.Application
$wScript = New-Object -ComObject WScript.Shell
# stack.push(...)
# I guess this is assuming the desktop isn't activated.
$shellApp.ToggleDesktop();
# This doesn't seem to be needed, but just in case:
#$desktopLoc = $wScript.SpecialFolders('Desktop');
#$wScript.AppActivate($desktopLoc);
#Give time to get to the desktop
sleep 1;
# Hack to make sure something is selected on the desktop
# if there is something
$wScript.SendKeys('{RIGHT}');
$wScript.SendKeys('{UP}');
$wScript.SendKeys('{LEFT}');
$wScript.SendKeys('{DOWN}');
# Toggles selection on desktop. Selected items have a
# different context window, so we want to have nothing
# selected
$wScript.SendKeys("^ ");
# Open a context menu and select the option to see the next slide
$wScript.SendKeys("+{F10}");
$wScript.SendKeys("n");
$wScript.SendKeys("~"); #I don't usually use the enter key, but when I do I use "~"
# Give the key commands time to be read
sleep 1;
# stack.pop()
$shellApp.ToggleDesktop();
}
Function Set-LockScreen($path) {
Set-ItemProperty -path 'HKCU:\Control Panel\Desktop\' -name wallpaper -value "$path"
rundll32.exe user32.dll, UpdatePerUserSystemParameters
}
Function Set-Theme ($theme) {
cmd /C ('rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:"C:\Users\admin\AppData\Local\Microsoft\Windows\Themes\' + $theme + '.theme"')
sleep 2;
# This is neat, bro
#$wScript = New-Object -ComObject WScript.Shell; $wScript.AppActivate('Personalization'); $wScript.SendKeys('%{F4}')
#$wScript = New-Object -ComObject WScript.Shell;
#$wScript.AppActivate('Personalization');
## This is kindof dicey
#$wScript.SendKeys('%{F4}')
# By default, it always (?) uses the alphabetically first picture
Next-Slide
$handle = $(Get-WindowHandle 'Personalization' 'CabinetWClass')
while ($handle.ToInt64() -le 0) {
sleep 0.5;
$handle = Get-WindowHandle 'Personalization' 'CabinetWClass'
}
Close-WindowHandle $handle
}
Function Flip-Screen() {
if ($(Get-Orient) -eq 0) {
Set-LeftOrient
sleep 1
Set-Theme vslidebg
} else {
Set-NormOrient
sleep 1
Set-Theme slidebg
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment