Desktop Background Slideshow in Windows Home Basic, with Powershell
I’m using Windows 7 Home Basic in my home computer, since this was the version that came bundled with the hardware. For the most part I don’t miss what the higher SKUs have to offer, although every now and then I’ll stumble at something missing and wish it was available. This is the case with the “desktop background slideshow”.
The desktop background slideshow
This is an interesting feature (available in all SKUs *above* Home Basic) which allows you to select a group of photos and have the desktop background change on an interval (say every 15m) to a new image. Quite interesting when you have a folder full of very nice images. But alas, Home Premium only allows you to point at a single image, no slideshow goodness for you.
One thing I don’t like about the implementation of the feature in Windows is that you have to select which images will be part of the whole slideshow, you doesn’t just point to a folder and forget. The consequence of this design decision is that if later you add new images to that folder they won’t immediately become part of the slideshow, you’ll have to come back to this dialog and add them in. Not ideal …
Hacking a slideshow on a budget
Changing the desktop background is not a very difficult proposition. There’s an API call which can do just that: SystemParametersInfo, in user32.dll (using the SPI_SETDESKWALLPAPER action parameter).
In earlier versions of Windows you were limited to BMPs for background, so even if the UI gave you de appearance of accepting any image file, behind the scenes a conversion would take place and what would get stored in the registry was the path to the converted image. In Windows 7 things are easier, you just set the desktop background to any image you’d like the OS takes care of the details.
A note about Windows XP
If you search the interwebs for scripted methods for changing the desktop background, you’ll come upon several references suggesting that you write to the registry key
HKEY_CURRENT_USER\Control Panel\Desktop\Wallpaper and then make a call to
UpdatePerUserSystemParameters method on
user32.dll, in order to let the OS know of the changed setting. Well, this method don’t work reliably on Windows 7 any longer: the registry key still controls the user’s desktop background, but the call to
UpdatePerUserSystemParameters no longer reliably updates the active session (once the user logs out and in again, things will be updated though). Bottom line, the correct (supported) method to update the desktop background and many other system parameters is through a call into
Putting it all together with Powershell
In order to accomplish a slideshow, we need to script the following sequence of opperations:
- List the images within a given folder
- Select one of the images at random
- SystemParametersInfo giving it the full path to the image
Any scripting language which would let you accomplish all of the above would do, but for the sake of this implementation I’ll use Microsoft’s Powershell, which comes pre-installed in Windows 7 (all SKUs, Home Basic included!). Powershell is a very nice scripting language overall, and it makes performing the steps above a real breeze:
Listing images within a given folder
$folder = "c:\Users\Public\Pictures\Wallpaper" Get-ChildItem $folder
And the results of the above would look something like this:
Diretório: C:\Users\Public\Pictures\Wallpaper Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 10/03/2011 14:45 522419 01178_chicagoatnight_1680x1050.jpg -a--- 10/03/2011 14:51 293592 01301_rubik_1680x1050.jpg -a--- 10/03/2011 14:58 390960 01338_crazysun_1680x1050.jpg -a--- 10/03/2011 14:45 556026 01356_crepuscule_1680x1050.jpg -a--- 10/03/2011 14:36 948187 01542_thebirthofeuropa_1680x1050.jpg -a--- 10/03/2011 14:57 687594 01603_droplets_1680x1050.jpg -a--- 10/03/2011 14:57 609453 01637_rb_1680x1050.jpg -a--- 10/03/2011 14:38 699733 01645_bergs_1680x1050.jpg -a--- 10/03/2011 14:51 1176550 01663_lakelouise_1680x1050.jpg -a--- 10/03/2011 14:59 1201259 01700_fallisintheair_1680x1050.jpg -a--- 10/03/2011 14:45 791767 01705_sunsetinfrontofme_1680x1050.jpg -a--- 10/03/2011 14:48 1124629 01729_monumentalsimplicity_1680x1050.jpg -a--- 10/03/2011 14:51 710181 01737_elemental_1680x1050.jpg
One of the nice things about Powershell, and which sets it apart from most other shell environments, is that although the output above appears as just a bunch of text, it is actually a collection of files, for which the default representation happens to be the listing above (technically, it is a System.IO.DirectoryInfo object, which has information about the directory itself, along with its contents). The significance of this is that instead of having to manipulate text in order to work with this result, I can just pass it along to other methods which work on collections of stuff and not have to worry at all about formatting. For the next step:
Selecting a single image, at random
Easier than taking candy from a kid: there’s a commandlet (a command, in Powershell parlance) which selects a random element from any list (any iterable collection), conveniently named Get-Random. Since the results of our directory listing are iterable, we can just pipe them through Get-Random:
Get-ChildItem $folder | Get-Random
And the result would be something like this:
Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 10/03/2011 14:51 1176550 01663_lakelouise_1680x1050.jpg
The resulting text output resembles that of the directory listing, but with only one entry. However, because we’re now referencing a single item in the listing, the object type for the result is System.IO.FileInfo – if the listing included sub-directories and one of them was selected at random, we’d have a result of type System.IO.DirectoryInfo here.
Calling the Win32 API to set the desktop background
Here’s where we hit a bit of a snag: PowerShell, as awesome as it is, don’t have an easy way to call into a native API. To solve this problem we could compile a managed or native executable to serve as a call proxy, and invoke this program from the script; but this would make for a solution which is not self-contained, and I’d rather have everything required in the script file itself, no extra dependencies.
And there is a way: we get some help from the
Add-Type cmdlet, which allows you to compile code inline and build new types at runtime. All we need is a suitable piece of C# code for PInvoking the user32 method; with a little help from pinvoke.net, we get the following definition:
[DllImport("user32.dll")] public static extern uint SystemParametersInfo( uint uiAction, uint uiParam, string pvParam, uint fWinIni);
Add-Type to compile and then invoke this method is very straight forward:
$type = Add-Type -MemberDefinition $signature ` -Name Win32Utils -Namespace SystemParametersInfo ` -PassThru $null = $type::SystemParametersInfo(20, 0, $fileName, 3)
Putting it all together in a single script
To conclude this part of the exercise, lets combine all steps above in a very long and complex script which will select a random image from a given folder and set it as the new desktop background:
$folder = "c:\Users\Public\Pictures\Wallpaper" $file = Get-ChildItem $folder | Get-Random $signature = @' [DllImport("user32.dll")] public static extern uint SystemParametersInfo( uint uiAction, uint uiParam, string pvParam, uint fWinIni); '@ $type = Add-Type -MemberDefinition $signature ` -Name Win32Utils -Namespace SystemParametersInfo ` -PassThru $null = $type::SystemParametersInfo(20, 0, $file.FullName, 3) "Wall paper is now set to $($file.FullName)"
Save this script in a text file with the extension “ps1” and you’re ready to execute it at will. Well, almost, as we’ll see:
Executing the script outside of the Powershell prompt
Once you have the script saved, you can execute it from any prompt as follows:
But chances are you’ll get an error like this:
O arquivo C:\Users\Public\Util\Shuffle_desktop_background.ps1 não pode ser carregado. O arquivo C:\Users \Public\Util\Shuffle_desktop_background.ps1 não está digitalmente assinado. O script não será executado no sistema. Consulte "get-help about_signing" para obter mais detalhes.. Em linha:1 caractere:31 + Shuffle_desktop_background.ps1 <<<< + CategoryInfo : NotSpecified: (:) , PSSecurityException + FullyQualifiedErrorId : RuntimeException
What just happened is that Powershell comes out of the box with a policy in place which onl allows for signed scripts to be executed. This is fine for most users, which will not fiddle with creating scripts of their own, but a bit too harsh for most power users. You can check your current execution policy by invoking the cmdlet Get-ExecutionPolicy, which should display AllSigned as its result.
To modify the policy you must open a Powershell prompt as administrator, then type the following command:
This call configures the system to require signatures in all non-local scripts, but allow the execution of unsigned local scripts. After this change, try re-running the image script and see what happens it should succeed this time.
Turning this one-time change into a proper slideshow
Now that we have the script, all we have to do in order to get a new desktop background every 15min is to remember to open the command line and execute this script once every 15min; pretty easy, uh? Well, if you’re anything like me, you’re lazy and want to assign this task to someone else; that someone else being your computer. Windows has a handy task scheduler built-in which is used for all kinds of pre-programmed tasks. You can create a task and have its action be the script call (powershell path-to-script). For the action trigger, either have it run every few minutes/hours, or at every logon, if you only want to change the background once every time you log onto the machine.
Getting rid of the Powershell Command Window Pop-up
As commenter Vikram pointed out (thanks!), when you invoke the Powershell interpreter to run the script, you get the Powershell command window to briefly show-up on your desktop. If you set an aggressive schedule for your task, like every 5min, this gets really annoying. Although the Powershell executable accepts a “-WindowStyle Hidden” parameter, the window will still flash briefly, before PS decides to hide it. Not good.
The best way that I’ve found to work around this issue is to have an intermediate program act as a Powershell host with no window, and use that to launch the script. This was suggested by Scott Weinstein in his post entitled Scheduling PowerShell tasks without a console window.
To implement his suggestion, in your Task Scheduler action, instead of calling Powershell.exe to run the script, you call the PoshExec.exe host you created following Scott’s instructions. And just in case you have any trouble creating the EXE from the source code, I’m leaving here a compiled version of the World’s Smallest Powershell Host Application that you can download (executable, source-code, MD5 hash).
To summarize, your action in TaskScheduler will look something like this:
- Action: Start a program
- Program/Script: c:\Path-to-program\PoshExec.exe
- Arguments: c:\Path-to-script\YourScript.ps1