Windows Powershell script to update your desktop [Moved]

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Prelates, Moderators General

Windows Powershell script to update your desktop [Moved]

Postby jsd1982 » Mon Mar 12, 2007 8:57 pm UTC

Despite the subject, this is relevant to xkcd.com. I was bored at work and wanted a black background and inverted xkcd.com toons to update automatically centered on my desktop. So, I wrote this little Windows Powershell script and set it to run as an automated event on log-on. Enjoy!

Code: Select all
# Script to load latest PNG from xkcd.com, invert it, and set it as the desktop background

function Compile-Csharp ([string] $code, $FrameworkVersion="v2.0.50727",
[Array]$References)
{
    #
    # Get an instance of the CSharp code provider
    #
    $cp = new-object Microsoft.CSharp.CSharpCodeProvider

    #
    # Build up a compiler params object...
    $framework = [System.IO.Path]::Combine($env:windir, "Microsoft.NET\Framework\$FrameWorkVersion")
    $refs = new-object Collections.ArrayList
    $refs.AddRange( @("${framework}\System.dll",
#        "${mshhome}\System.Management.Automation.dll",
#        "${mshhome}\System.Management.Automation.ConsoleHost.dll",
        "${framework}\system.windows.forms.dll",
        "${framework}\System.data.dll",
        "${framework}\System.Drawing.dll",
        "${framework}\System.Xml.dll"))
    if ($references.Count -ge 1)
    {
        $refs.AddRange($References)
    }

    $cpar = New-Object System.CodeDom.Compiler.CompilerParameters
    $cpar.GenerateInMemory = $true
    $cpar.GenerateExecutable = $false
   $cpar.CompilerOptions = "/unsafe";
    $cpar.OutputAssembly = "custom"
    $cpar.ReferencedAssemblies.AddRange($refs)
    $cr = $cp.CompileAssemblyFromSource($cpar, $code)

    if ( $cr.Errors.Count)
    {
        $codeLines = $code.Split("`n");
        foreach ($ce in $cr.Errors)
        {
            write-host "Error: $($codeLines[$($ce.Line - 1)])"
            $ce |out-default
        }
        Throw "INVALID DATA: Errors encountered while compiling code"
    }
}

[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $null
[System.Reflection.Assembly]::LoadWithPartialName("System.Runtime") > $null

# Get the RSS feed from xkcd.com
$rssURL = "http://xkcd.com/rss.xml"
$blog = [xml](new-object System.Net.WebClient).DownloadString($rssURL)

# Pull the first item's description string, which should be an <img src="..."... />
$desc = $blog.rss.channel.item[0].description

# Grab the URL from the 'src' attribute of the 'img' tag:
$ix1 = $desc.IndexOf("src=") + 5
$imgurl = $desc.Substring($ix1, $desc.IndexOf('"', $ix1) - $ix1)

# C# code to invert colors and post to wallpaper
$code = @'
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
using System.Net;
using Microsoft.Win32;

namespace test
{
    public class Wallpaper
    {
      const int SPI_SETDESKWALLPAPER = 20  ;
      const int SPIF_UPDATEINIFILE = 0x01;
      const int SPIF_SENDWININICHANGE = 0x02;

      [DllImport("user32.dll", CharSet=CharSet.Auto)]
      static  extern int SystemParametersInfo (int uAction , int uParam , string lpvParam , int fuWinIni) ;

      public static void SetInverted(string uri)
      {
         System.IO.Stream s = new WebClient().OpenRead(uri);

         Image img = System.Drawing.Image.FromStream(s);
         Bitmap copy = new Bitmap(img.Width, img.Height);
         Graphics g = Graphics.FromImage(copy);
         Rectangle rect = new Rectangle(0, 0, img.Width, img.Height);
         g.DrawImage(img, rect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
         g.Dispose();
         img.Dispose();

         // Invert colors:
         unsafe {
            BitmapData bmd = copy.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, copy.PixelFormat);
            int PixelSize = 4;

            for(int y=0; y<bmd.Height; y++) {
               byte* row=(byte *)bmd.Scan0+(y*bmd.Stride);
               for(int x=0; x<bmd.Width; x++) {
                  row[x*PixelSize] = (byte)(255 - row[x*PixelSize]);
                  row[x*PixelSize+1] = (byte)(255 - row[x*PixelSize+1]);
                  row[x*PixelSize+2] = (byte)(255 - row[x*PixelSize+2]);
               }
            }
            copy.UnlockBits(bmd);
         }

         // Save to a temp file:
         string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper.bmp");
         copy.Save(tempPath, System.Drawing.Imaging.ImageFormat.Bmp);

         RegistryKey key = Registry.CurrentUser.OpenSubKey( @"Control Panel\Desktop", true ) ;
         key.SetValue(@"WallpaperStyle", 1.ToString( ) ) ;
         key.SetValue(@"TileWallpaper", 0.ToString( ) ) ;

         SystemParametersInfo( SPI_SETDESKWALLPAPER,
            0,
            tempPath, 
            SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE );
      }
   }
}
'@

compile-CSharp $code
[test.Wallpaper]::SetInverted($imgurl)

As usual, you need to sign the code for it to run on your machine.
User avatar
jsd1982
 
Posts: 32
Joined: Mon Mar 12, 2007 8:53 pm UTC

Postby Belial » Mon Mar 12, 2007 8:59 pm UTC

Mind posting in the intro thread? Standard bot-avoidance procedure.
addams wrote:A drunk neighbor is better than a sober Belial.
User avatar
Belial
A terrible sound heard from a distance
 
Posts: 30232
Joined: Sat Apr 15, 2006 4:04 am UTC

Postby aisling » Mon Mar 12, 2007 9:28 pm UTC

I... I... I wish I knew more about computers.

My god. I think I'll get my dad to take a look at this and put it on our computer.

This is SO COOL.
User avatar
aisling
Has (underage) boobs
 
Posts: 735
Joined: Mon Jan 01, 2007 5:56 am UTC
Location: Ontario, Canada

Postby TheTankengine » Mon Mar 12, 2007 10:36 pm UTC

Wow, that just seems super-fucking-complicated. Anyone want to post the much more efficient Bash script?
be centered
be compassionate
be interesting
User avatar
TheTankengine
Our Fora-father
 
Posts: 3328
Joined: Tue Oct 17, 2006 2:09 pm UTC
Location: Louisville, KY

Postby warriorness » Mon Mar 12, 2007 11:23 pm UTC

Nobody need bother with the bash one, as it's so simple a monkey could do it.

See, this is the subtle advantage of Linux that MS fanboys don't seem to get in OS wars topics.
Iluvatar wrote:Love: Gimme the frickin' API.
yy2bggggs, on Fischer Random chess wrote:Hmmm.... I wonder how how a hypermodern approach would work
User avatar
warriorness
Huge Fucking-Lazer
 
Posts: 1610
Joined: Thu Dec 28, 2006 10:33 am UTC
Location: CMU, Pittsburgh, PA, USA

Postby EvanED » Mon Mar 12, 2007 11:54 pm UTC

warriorness wrote:Nobody need bother with the bash one, as it's so simple a monkey could do it.

See, this is the subtle advantage of Linux that MS fanboys don't seem to get in OS wars topics.


Only if you use an external program like ImageMagick or something like that. While this is part of the elegance of Unix, you could do that on Windows too. (Over a third of the length of that is just doing the color inversion.)

But it would probably be much simpler.

(Though how do you suggest doing the parsing of the config files for whatever desktop environment you use? I don't know what Bash gives you there.)
EvanED
 
Posts: 4160
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI

Postby jsd1982 » Tue Mar 13, 2007 12:33 am UTC

Belial wrote:Mind posting in the intro thread? Standard bot-avoidance procedure.

Done and done. Sorry I was in a rush at work to post my script. :)

The inverting of the colors was the trickiest part - had to compile embedded C# code from the script itself and run the in-memory class to do so performantly. I did have an implementation without the C# code but it took forever to run, doing GetPixel/SetPixel on the Graphics object... yeesh, no pointers in script.

This is naturally for Windows only and the tough part is having to sign the code - there's no easy way out of it unless you set your security mode to trust all.
User avatar
jsd1982
 
Posts: 32
Joined: Mon Mar 12, 2007 8:53 pm UTC

Postby EvanED » Tue Mar 13, 2007 12:43 am UTC

jsd1982 wrote:
Belial wrote:Mind posting in the intro thread? Standard bot-avoidance procedure.

Done and done. Sorry I was in a rush at work to post my script. :)

The inverting of the colors was the trickiest part - had to compile embedded C# code from the script itself and run the in-memory class to do so performantly. I did have an implementation without the C# code but it took forever to run, doing GetPixel/SetPixel on the Graphics object... yeesh, no pointers in script.

This is naturally for Windows only and the tough part is having to sign the code - there's no easy way out of it unless you set your security mode to trust all.


If you could get rid of that, would you be able to get rid of the whole Compile-Csharp function?

I must admit to not using MSH at all.
EvanED
 
Posts: 4160
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI

Postby jsd1982 » Tue Mar 13, 2007 2:06 pm UTC

EvanED wrote:If you could get rid of that, would you be able to get rid of the whole Compile-Csharp function?

I must admit to not using MSH at all.

I'm not sure I can get rid of the embedded C# as it calls to the Win32 API to refresh the desktop. Or were you referring to the code-signing?
User avatar
jsd1982
 
Posts: 32
Joined: Mon Mar 12, 2007 8:53 pm UTC

Postby shadebug » Tue Mar 13, 2007 2:08 pm UTC

I'd use feh for getting the background up from a bash script. Obviously another program is needed but that's just because windows bloats it all into one big program they like to call windows
Heaven is for the hedonist
User avatar
shadebug
 
Posts: 425
Joined: Sat Jan 20, 2007 3:21 pm UTC
Location: Ceredigion/Essex, UK

Postby EvanED » Tue Mar 13, 2007 3:41 pm UTC

shadebug wrote:I'd use feh for getting the background up from a bash script. Obviously another program is needed but that's just because windows bloats it all into one big program they like to call windows


Don't you mean "Windows gives you the flexibility to bloat it all into one big program if you want to and deem it best while Bash forces you to break it up?"

(I'm slightly trolling, but really, Windows isn't as bad as most of you think.)
EvanED
 
Posts: 4160
Joined: Mon Aug 07, 2006 6:28 am UTC
Location: Madison, WI

Postby warriorness » Tue Mar 13, 2007 4:14 pm UTC

EvanED wrote:
warriorness wrote:Nobody need bother with the bash one, as it's so simple a monkey could do it.

See, this is the subtle advantage of Linux that MS fanboys don't seem to get in OS wars topics.


Only if you use an external program like ImageMagick or something like that. While this is part of the elegance of Unix, you could do that on Windows too. (Over a third of the length of that is just doing the color inversion.)

But it would probably be much simpler.

(Though how do you suggest doing the parsing of the config files for whatever desktop environment you use? I don't know what Bash gives you there.)


Well, the beauty of ImageMagick and other similar apps is that they have a command line interface. Inverting the color would be very simple (assuming that IM does in fact have a way to invert...).

As for the desktop environment - you could have it detect which WM is running:
Code: Select all
if $(pidof kdesktop) != ""
     # stuff goes here
fi

Or something of the sort - this would tell you if KDE is running. (My bash-fu is weak, so the syntax may not be right). Then you do something similar for other common DEs (gnome, xfce, *box, fvwm, etc) and you'd probably use grep and sed to edit the appropriate config file.

And you could use the cron daemon to schedule it to run at midnight on MWF.
Iluvatar wrote:Love: Gimme the frickin' API.
yy2bggggs, on Fischer Random chess wrote:Hmmm.... I wonder how how a hypermodern approach would work
User avatar
warriorness
Huge Fucking-Lazer
 
Posts: 1610
Joined: Thu Dec 28, 2006 10:33 am UTC
Location: CMU, Pittsburgh, PA, USA

Postby aldimond » Tue Mar 13, 2007 10:18 pm UTC

Wait, why are you guys worried about configuration files and shit?

All you have to do is run whatever that program is, xsetbg or xsetroot, I don't remember. Just put it in your $HOME/.xinitrc and forget about it.

Unless you have some program running that overcomplicates it. Which actually is very common in WMs/DEs these days, and that's sad. A WM shouldn't be touching your desktop picture. A DE might have a better case, but hopefully there's an interface that doesn't require parsing a config file to do a script like this. But I really think that a script shouldn't be multiplexed over every possible DE you might be running; it's not that you're writing a bash script, you're only using bash to make a bunch of calls in order; you should have a KDE dude write a script for KDE, a GNOME dude write a script for GNOME, and a sane motherfucker write a script that works for people without DEs. And for those with WMs that try to act like DEs, they should fire their WMs.
One of these days my desk is going to collapse in the middle and all its weight will come down on my knee and tear my new fake ACL. It could be tomorrow. This is my concern.
User avatar
aldimond
Otter-duck
 
Posts: 2665
Joined: Fri Nov 03, 2006 8:52 am UTC
Location: Uptown, Chicago

Postby davean » Tue Mar 13, 2007 10:35 pm UTC

Why didn't you use the dirrect link to the current comic like any sane person?
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC

Postby davean » Tue Mar 13, 2007 10:56 pm UTC

aldimond wrote:All you have to do is run whatever that program is, xsetbg or xsetroot, I don't remember. Just put it in your $HOME/.xinitrc and forget about it.


Nah, you stick the two command line in cron, since the comic updates.

such as:

15 0 * * 1,3,5 wget -q http://xkcd.com/current/ -O /tmp/xkcd_background && xsetbg -fullscreen /tmp/xkcd_background

Unlike the above this does not break when other entries occur in RSS (as they occasionally do) and handles errors gracefully (including not messing up the background in case of failure).

Yes there are a lot of better ways to do this, but this is about the simplest.

Furthermore, it keeps it up to date without excess retries. (take your timezone into account, this is EST5EDT)
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC

Postby aldimond » Tue Mar 13, 2007 11:04 pm UTC

davean wrote:
aldimond wrote:All you have to do is run whatever that program is, xsetbg or xsetroot, I don't remember. Just put it in your $HOME/.xinitrc and forget about it.


Nah, you stick the two command line in cron, since the comic updates.


Yeah, I really failed at expressing myself there (I was trying to say that I didn't remember which program it was or its switches because I never think about it because it's stuffed away in my .xinitrc). Given cron or similar service it definitely seems like The Right Way to me.

Just for the record, you say that there are other better ways but that command is the simplest; if that method gets the job done and is the simplest how could any other method be better?
One of these days my desk is going to collapse in the middle and all its weight will come down on my knee and tear my new fake ACL. It could be tomorrow. This is my concern.
User avatar
aldimond
Otter-duck
 
Posts: 2665
Joined: Fri Nov 03, 2006 8:52 am UTC
Location: Uptown, Chicago

Postby davean » Wed Mar 14, 2007 12:05 am UTC

aldimond wrote:
davean wrote:
aldimond wrote:All you have to do is run whatever that program is, xsetbg or xsetroot, I don't remember. Just put it in your $HOME/.xinitrc and forget about it.


Nah, you stick the two command line in cron, since the comic updates.


Yeah, I really failed at expressing myself there (I was trying to say that I didn't remember which program it was or its switches because I never think about it because it's stuffed away in my .xinitrc). Given cron or similar service it definitely seems like The Right Way to me.

Just for the record, you say that there are other better ways but that command is the simplest; if that method gets the job done and is the simplest how could any other method be better?


It only handles one method of setting the background, if the site where down (which it will never be, but maybe your internet connection is out or you own a mobile device like a laptop) it wouldn't reschedule a second attempt for later, it doesn't do any intelligent transforms to try to make the desktop image look it's best. Furthermore, it does not give me a massage.
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC

Postby davean » Wed Mar 14, 2007 12:06 am UTC

Oh yah, and it uses a named file, a no-no in scripting, worse, it uses one in /tmp where multiple copies of this script could walk over each other.
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC

Postby aldimond » Wed Mar 14, 2007 12:44 am UTC

Hopefully you only need one method to set the background. Choosing the right one for your system and just using it is better than involving a bunch of silly conditionals that will always evaluate the same.

Not scheduling another attempt is not a bug in this particular configuration of cron. Doing stuff like that would be an additional feature. You can make the same thing happen on boot/resume-from-standby/network awakening by adding a similar line to some other file, which is an additional feature. To avoid duplicating code you'd probably then move the actual commands to a file... and only then it starts to get more complicated and you have to worry about multiple copies of the script running. Whether you want to incur that complexity is your choice. I'd keep it simple and take the risk that somehow wget is going to take an entire day to run.

Adding size transformations/borders/etc. is trivial to the point that I wouldn't even consider doing so a real change of method. Doing such a thing intelligently falls into the category of silly conditionals that will always evaluate the same.

An automatic desktop setter that attempted to give massages would be broken to the point that it would be easier to write a new one (even using Powershell!) than to fix it. One that gave massages as an unintentional side effect would be troubling and you'd probably want to investigate the cause of the massages and break them off into a seperate program if possible.

At first I didn't think there was any logical reason to care about named files in this case (rules of scripting notwithstanding... what's important isn't that it's a rule but why it's a rule) but now I guess it could be a problem. If there's a security hole in xsetbg or any of the graphics libraries it calls (a near certainty) it could be exploited by putting bad data there at the right time, and if there's a security hole in your mind (think Snow Crash... or maybe goatse if you really want to think that) the same scenario applies.
One of these days my desk is going to collapse in the middle and all its weight will come down on my knee and tear my new fake ACL. It could be tomorrow. This is my concern.
User avatar
aldimond
Otter-duck
 
Posts: 2665
Joined: Fri Nov 03, 2006 8:52 am UTC
Location: Uptown, Chicago

Postby davean » Wed Mar 14, 2007 3:52 am UTC

No, but choosing which does need to be done. And this doesn't, it just assumes wget will work there is no background setter parameter.

Yes, it is an added "feature" to handle edge conditions ... if you don't care that it can totally fail and leave you in an invalid state ... the && will only save you from an error code from wget, which may be 90% of errors but is fart short of perfect.

Each comic is different, the if statements will all be used quite quickly. Whether to scale, center, or whatnot and how much and how so is a complex decision based on a lot of factors about each specific image.

The standard practice in background scripts such as cron is to email failures to the system administrator.

Also, finally, there is another issue with named files you failed to note, especially using tmp in this way. What if several users on the system all use this same script? There are of course pages of reasons not to use named files.
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC

Postby jsd1982 » Wed Mar 14, 2007 4:24 am UTC

davean wrote:Why didn't you use the dirrect link to the current comic like any sane person?
You assume I'm aware of said link. If I were obviously I would not have read from the RSS feed and "parsed" the assumed HTML.

I don't see that link anywhere from the homepage, btw. How does one find out about this?
User avatar
jsd1982
 
Posts: 32
Joined: Mon Mar 12, 2007 8:53 pm UTC

Postby davean » Wed Mar 14, 2007 5:42 am UTC

You can search the forums, it has been mentioned here several times.
User avatar
davean
Site Ninja
 
Posts: 2443
Joined: Sat Apr 08, 2006 7:50 am UTC


Return to Coding

Who is online

Users browsing this forum: No registered users and 3 guests