Search

my categories

Blogroll

Searching PoshCode repository from your Desktop in Windows 7

February 16th, 2009 by Karl

A picture says a thousand words

 

dvxxlk 

Search results from the PowerShell Code Repository right there on your desktop in windows 7 thanks to federated search and Jaykul adding OpenSearch support to PoshCode. YAY

So how do you get this. Simply download this link  and save it as fileextention .OSDX to your windows 7 machine. Then Run it. 

That is a simple OSDX file that is just an XML file describing the OpenSearch interface. the full text is here. and it comes from PoshCode itself.

<?xml version="1.0" encoding="UTF-8" ?>
<OpenSearchDescription xmlns="
http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  <ShortName>PoshCode</ShortName>
  <Description>PowerShell script repository search interface</Description>
  <Tags>PowerShell Scripts Scripting Code Modules</Tags>
  <Contact>feedback@poshcode.org</Contact>
  <Url type="text/html" template="
http://poshcode.org/search/{searchTerms}" />
  <Url type="application/rss+xml" template="
http://poshcode.org/api/{searchTerms}" />
  <Image height="16" width="16" type="image/vnd.microsoft.icon">
http://poshcode.org/favicon.ico</Image>
<!–   <Url type="application/x-suggestions+json" template="
http://localhost:7402/content/notes.opensearchsuggestions.json?qt={searchTerms}"/>

  –>
  </OpenSearchDescription>

Posted in Powershell, poshcode | No Comments »

Sick of an exception happening to just one item in the pipeline when you don’t care.

February 13th, 2009 by Karl

How many times have you had something like:

get-thousandsofitems | % { do-something $_ } | whatever

Only to have some exception happen in do-something and it throws the whole thing. When really you don’t care, you’d rather just be able to know what failed and have it move on. This happens a fair bit with Get-WMIobject etc, because one of the machines may be off, firewall blocked, some sort of permissions issues – things that bubble up even when you have the preference set to SilentyContinue.

So what do we normally do in this scenario. We either pipeline it and make it look like a VBscript, or ruin our beautiful single liner by making that script complicated with V1 traps and the likes, and we usually get the error handling wrong wrong a number of times along the way.

Well with PowerShell you can always adjust and expand the language. There is nothing particular special about the foreach-object Cmdlet. In its common use you can replicate it with a function as simple as the one below.

function myforeach-object([scriptblock]$process) { process { &$process } }

So how about making a looping function that deals with the exceptions for you, you say? Great Idea!, Now you are cooking. The following function is pretty much a simple V1 function, though it should really be completed and production readied into a V2 Advanced function with all the modern trimmings and trappings, however I DO use V2 TRY/CATCH syntax instead of V1 Trapping, because its more sane to us who don’t have a secret VBscript fetish.

function foreach-withexception ([scriptblock]$process,$outputexception)
{
  begin { $global:foreachex = @() }
  process {
            try
            {
            $local:inputitem = $_
            &$process
            }
            catch
            {
            $local:exceptionitem = 1 | select-object object,exception
            $local:exceptionitem.object = $local:inputitem
            $local:exceptionitem.exception = $_
            $global:foreachex += $local:exceptionitem
            }
          }
  end {}
}
set-alias %ex foreach-withexception

So i love my aliases , %ex for foreach with exceptions. Really I wish i could come up with a better name for the long function. I also keep a %i for my foreach-index function as well. Basically you use this just like foreach-object (though i haven’t implemented a begin and end area in this), and exceptions get eaten, but after the fact you can access the object that caused the error, and the error in the global $foreachex variable. At a later date i’m going to add an option paramter variable that if can put the exception in instead of a naughty global variable. But as Jeffrey Snover says “To ship is to choose” though for me it really means “this function does what i need it to now, i need to go back and solve the Real problem i’m solving that this function really helps with”

here is an example

100..-5 | %ex {  "yo $_" ;  1 / $_ }

$foreachex

Capture1

normally 0/0 would throw a fuss , and kill the whole thing, but it just works on , and you can check to see if there are any errors in $foreachex Normally the things that i need this for though are “loose web services”, WMI, or networking sort of things. but really it simplifies my one liners, and gets things done quicker.

@poshcode | Download

 

Posted in PS extended, Powershell | No Comments »

PowerBoots – a beautiful DSL , not to mention useful.

February 12th, 2009 by Karl

I’ve always liked the fact that I can extend PowerShell, whether its just a little bit of syntax here or there, or a full blown DSL, and end up with something that is still naturally powershelly. Jaykul (Joel Bennet) has really used PowerShell in such a way. really making something that is pithy, follows a DRY philosophy, and is METAPROGRAMMING on two levels (the auto generation of the PowerShell functions and parameters, and of course the XAML). Its not only a beautiful example of PowerShell as a DSL, but also ultimately useful. I really think it has the potential to become the tcl/tk of PowerShell and the primary admin GUI scripting framework. I’m so impressed with it, we at ShellTools are definitely going to adjust our PowerShell WPF designer to support Boots. Boots is also now a codeplex project

Below is an example of the script, and a few screenshots and videos from Jaykul’s blog.

Boots {
   StackPanel -Margin 10 {
      TextBlock "The Question" -FontSize 42 -FontWeight Bold -Foreground "#FF0088"
      TextBlock -FontSize 24 {
         Hyperlink {
            Bold "Q. "
            "Can PowerBoots do async threads?"
         } -NavigateUri " " -On_RequestNavigate { $global:Answer[0].Visibility = "Visible" }
      }
      TextBlock -FontSize 16 {
         Span "A. " -FontSize 24 -FontWeight Bold
         "Oh yes we can!"
      } -OV global:Answer -Visibility Collapsed
   }
} 

.

PowerBoots11.png

.

Posted in Powershell, powerboots | No Comments »

Print-File Function

February 10th, 2009 by Karl

Here is a simple function that will ask the associated program to print any file you pass in. It can work with a file as a parameter, or multiple files. How well this works, especially with multiple files at a time, depends on the associated application, first whether it supports the print verb, and secondly how it handles it – more importantly how it handles opening many files at once. I haven’t yet been brave enough to try it on PDFs with adobe reader.

function print-file($file)
{
 begin  {
    function internal-printfile($thefile)
    {
        if ($thefile -is [string]) {$filename = $thefile }
        else {
                if ($thefile.FullName -is [string] ) { $filename = $THEfile.FullName }
             }
        $start = new-object System.Diagnostics.ProcessStartInfo $filename
        $start.Verb = print
        [System.Diagnostics.Process]::Start($start)
    } 

if ($file -ne $null) {
                $filespecified = $true;
                internal-printfile $file
            }
       }
process{if (!$filespecified) { write-Host process ; internal-printfile $_ } } 

}

@poshcode | download

Here you can pass in a path to the file , or a fileobject that you get as a response from get-childitem (dir)

Here are a few examples

#look recursively through a folder and print all word documents
dir *.doc -r | print-file 

#print one particular pdf file
print-file c:\downloads\myfile.pdf

Warning, this isn’t up to being a shrink wrapped deliverable. It doesn’t do any error handling, isn’t a nice V2 advanced function or anything, but that’s a beauty of PowerShell, you can just hack out something that fits your needs in a few minutes. With V2 parameter binding, this would be more elegant and actually simpler as i have to do some duplication of logic in my function to get it to work accepting the file both through the pipeline and as a parameter.

-Enjoy.

Posted in PSV2, Powershell, script | No Comments »

A series of almost unfortunate events.

February 6th, 2009 by Karl

So I came home and wanted to make sure that everything would go smooth for ringing into the PowerScripting Podcast.

1) The first thing I did was go to plug in my cell phone charger, however I plugged it into a dodgy outlet that the landlord was meant to have replaced months ago. We’ll I haven’t been electrocuted that bad since the time in China when I jumped out of the bath, and came in contact with live wires. Thankfully i’m all ok, but half of metal of charger plug is gone. It was quite odd, it looked like a spark that flew out, but stayed bright for like 10 seconds. It must have been heated up melted metal.

2) Second thing i managed to do was lock myself in a room, with little kids in the other part of the house, while my wife was out. Anyhow during the podcast (after i managed to get on it since i had given the hosts the wrong area code of my phone number) I managed to pick my way out with a sewing measuring tape. But maybe i should have just stayed there because the third thing takes the cake.

3)So it was time to go and pick my daughter up from “Daisies”. I got in our minivan , pulled a perfect u turn off at the end of the street, then I saw our cat run up a tree and wanted to grab it. So I pulled up abruptly put the van in park, then jumped out quickly with the engine still running, HOWEVER IT WASN’T IN PARK BUT IN REVERSE So there was my van driving itself backwards down the street. I had to run after it, and after a brief attempt to stop it with my brute force, i jumped in and put the break on.

Finally i read a comment on the podcast ustream chat that the hosts may have lost the whole interview. Sounds like a true Wacky Wednesday .

I just thank God that i’m alive, and not hurt, and that i still have a complete car and our neighbours still have complete houses.

-Karl

Posted in Powershell | 4 Comments »

PowerShell ISE-Cream

February 5th, 2009 by Karl

A while back I threw together a few scripts to enhance PowerShell V2 ISE experience that added some PowerShell Analyzer like features . They were a natural fit as ISE (Intergrated Scripting Environment) is like a WPF-based stripped PowerShell Analyzer like with its multiple runspaces, editors, and immediate command area.

The extensibility model of ISE has caught on, and many people have been writing some great scripts adding hotkeys to do every what not you could imagine, so I’ve decided to start a codeplex project with the punny name “PowerShell ISE-Cream” . The Goal of this project is first to gather a huge variety of functionality that various people are willing to share, refactor it into production quality functions, with extensive error handling, and packaged in V2 Modules, and finally incorporate it in a way that users can easily turn on and off different features through a consistent configuration.

Right now however we are just going to gather whatever scraps there are, whether they conflict with each other or not, and anybody passionate about this can join the project, as we sculpt it hopefully into something both useful and beautiful.

Doug Finke has already joined the club with his Expand-Alias and Expand-CurrentAlias functions, and there are quite a few other functions that people i know are willing to share, so it should be good.

We’ll be brainstorming and deciding on a bunch of design issue, you can join the conversation on the first one in this discussion thread.

The PowerScripting Podcast tonight (Thursday 5th Feb 2009) will be talking about ISE. There should be a number of MVPs (including myself) and some PowerShell Team staff calling in. It Should be fun.

-Karl

Posted in PSV2, Powershell, pscom | 1 Comment »

Tobias and Idera make PowerShellPlus 2.1 Beta Public.

February 3rd, 2009 by Karl

Well its been six months or more since our baby PowerShell Plus grew up, left home and moved in with Idera. Well its been progressing nicely, and we are happy to see the vision being fulfilled,nd even expanded, with a successful commercial product. Go Tobias – Go Idera.

Anyhow, I’m pretty much going to reblog the content from Tobias’s blog entry below. If you haven’t tried PS+ yet. I recommend wholeheartedly that you do. It really is about 3 generations and a couple of years ahead of anything else out there. Other vendors have only recently implemented “borrowed” features that have been in PowerShell Analyzer and PowerShell Plus since 2006.

http://powershell.com/cs/blogs/news/archive/2009/02/02/powershellplus-v2-1-beta-is-live.aspx

———————————————————

Exciting news! Effective immediately, PowerShellPlus v2.1 Beta is live and publicly available! Below you will find exciting information about an early look at what the PowerShellPlus team has been working on.

PowerShellPlus version 2.1 introduces several new and cool features focused on reducing the PowerShell learning curve, increasing the productivity of PowerShell development and exercising the capabilities of PowerShell 2.0. The new features include:

  • Code sharing – Enables you to leverage the wealth of scripts that reside in the PowerShell.com and PoshCode.org libraries. Increase your productivity by quickly and easily searching and grabbing the scripts from those libraries. You can also submit your own scripts to the PowerShell.com library directly from PowerShell Plus Editor
  • Visual Basic support – The PowerShell Plus Editor can now edit and run Visual Basic scripts
  • STA mode support – Enables you to produce pretty cool looking GUI’s with Windows Presentation Framework (WPF) using PowerShell 2.0.
  • PowerShell Assembly Detection – Gives the Learning Center an auto-upgrade path if PowerShell v2 CTP3 is detected and then displays the most up-to-date information
  • Learning Center Auto-Load - Makes searching for a request with a single match much easier to use since the topic will load automatically, saving extra keystrokes
  • Cmdlet parameter position – Shows you additional information in the code completion popup window about parameters including position and type
  • Console size overlay – Shows you the height and width of the Console when you resize it
  • Additional Sample Scripts - Active Directory, IIS 7.0 and MySQL
  • PowerShell v2 CTP3 Support – Including Block Comments and the $Profile variable

How to get the New Version

Use the link at the bottom of this page to download the installation package, but before you do please read the following disclaimers, warnings and other general portents of doom.

  • This software is a pre-release version and should not be deployed in a production environment. It will not work the way a final version of the software does. Features will change before the final release
  • If you already have a PowerShellPlus license we recommend that you install this version on another machine, but we do support an upgrade in place your production version.
  • If you upgrade your production version of 2.0 we will not overwrite any user customizations that have been made to PowerShellPlus but we do recommend you make a copy of the Sample scripts if you have modified them.
  • A list of the changes is provided in the Release Notes in the Installation Package.
  • This release is designed to provide the PowerShellPlus community with a preview of key features coming in 2.1 and to solicit feedback about them. This Technical Beta is not supported by the Idera Technical Support Team. Please visit the Beta Place: http://powershell.com/cs/forums/93.aspx

System Requirements

  • Microsoft Windows PowerShell Version 1 or Version 2 CTP2 or CTP3
  • Windows XP, Server 2003, Windows Vista, Server 2008, Windows 7

Installation Instructions

To install PowerShellPlus:

  1. Click the link at the bottom of this page and complete the download form
  2. An email will be sent with instructions on how to download the Beta.
  3. After downloading the Beta, unzip the contents of the Installer package.
  4. Read the Release Notes for late breaking information about the Beta.
  5. If you are upgrading from PowerShellPlus 2.0 make that you copy the Sample Scripts to another location, if you have modified them.
  6. Run the Setup.exe program to install PowerShellPlus 2.1.

Grab your copy now:

Download Here

For feedback, bug reports, suggestions and discussions regarding this beta release, please visit and use our Beta Place:

http://powershell.com/cs/forums/93.aspx

Enjoy!

-Tobias

Posted in PSV2, Powershell, Powershell Plus, pscom | No Comments »

Building an ASP.NET MVC Twitter app over 24 hours.

February 1st, 2009 by Karl

I want to make a asp.net MVC twittercentric web app in 24 hours. I had initially thought that I’d do 24 hours in a stretch, but as an aging (31) family man I don’t think I will be able to pull that off, so its going to be stints.

So my starting point:

WHAT HAVE I GOT SO FAR?
    -I’ve decided on the platform
    -the idea and name of the app
    -written a few thoughts, notes down,
    -registered the twitter account that its going to be based around.
    -I am a proficient DOTNET developer, but a fair bit of asp.net experience, but I wouldn’t say an ASP.NET expert, and I haven’t touched ASP.NET MVC. I have however been reading about it during its development, and grok the concepts.
   - I’ve brainstormed many aspects of the features, design and architecture probably for more than a couple of hours in the background whilst doing real life.

WHAT HAVEN’T I DONE YET?
   -I don’t even have it installed in my development environment (which will be my first task).

WHAT I DON’T THINK WILL BE IN THE SCOPE OF THIS ALPHA?
   -Allowing the user to pass in their twitter credentials. I’m going to force any account related stuff to be sent via tweets from their account. I don’t want to even temporarily have twitter credentials pass through my server, until an app is fleshed out, tested, and secured.
   -Won’t be on the public internet - it will be running on localhost on my dev box.
   -Making it look pretty - I’ll just choose some basic ASP.NET MVP template to start with, and even "borrow" images from http://image.google.com while I flesh out the app. Of course once the first round of code is done, I’ll work on themeing it myself.
   -Won’t have any web based jobs updating my database with twitter responses. I’ll probably just have a build that part of it out in a powershell script on a recurring job for the first alpha.
   - Won’t have any ajaxy behavior. Jquery will be there, and may be used for a few visual things if it takes a short period of time, but this is pretty much just simple page requests showing data. However of course the 2nd alpha should be in more depth.
  - Will keep basic views, won’t use master pages or anything else.

WHAT I PLAY TO BE IN THE SCOPE OF THIS ALPHA.
-Allowing the user to issue commands through twitter ( with replies or direct message).
-Allowing the user to issue and record a few different types of information.
-Have a web page that displays the data related to this app, that includes graphs and charts
-Data will be retrieved by LINQ2SQL, but that LINQ2SQL will probably be simple, and for complicated queries will just call stored procedures.
-

FIRST STEPS. (some of these can be done concurrently )
-Download ASP.NET MVC and get a build in test project running.
-Choose an ASP.NET MVC template to start with
-Start the first ASP.NET MVC proof of concept. Basically get full round trip. From request->routing->controller->model (which will get data from DB) and back to view sort of thing.
-Start database and model design
-Start building powershell scripts to query twitter, and update the database.
-Fleshing out command syntax and scope limiting features.

I know I haven’t even said what this application will do, but that will come in time. I don’t plan on spamming by blog with lots of updates on this so I’ll be running a tubmlr blog for that. http://geekkarl.tumblr.com/ and will also be doing small summaries on twitter http://twitter.com/karlprosser . I’ll also be hanging out as usual in the #powershell IRC room on freenode.com and hopefully not getting too distracted there

-Karl

Posted in Powershell | No Comments »

Debugging Realtime by Ear. Adding one more dimension

January 30th, 2009 by Karl

I am amazed by people who can pick out every single note and chord when listening to music, and others who can totally play by ear. However even the musically inept and tone-deaf of us can detect an odd note, especially if its in a tune we know, but often even in an unknown tune, we can detect an out of place note, that isn’t in the scale, whether we know what a scale is, or even what key the song is in.

So what’s this got to do with debugging? We’ll mostly it’s the real time aspect. You are able to process a lot of information (notes), in context, and are able to discern real-time what is out of place and wrong.

Compare that to typical debugging. You might use checkpoints, and tracing through code, slowing down the execution often a million-fold, tediously stepping through each line of code, looking here and there.
Alternatively you might be processing log output, whether a debug stream in your IDE, console output, or something else. However just getting those messages to somewhere you can display can be hard when threading, and you aren’t in the GUI thread etc.
Additionally you are having to interpret this, often after the fact, and you can often miss things when lots is going on. How often , especially when finding a rare threading bug, are you running something 10 or more times, and maybe logging 10 or so different events each time. Going through the log takes time, and sometimes you miss the cue.

So, is debugging with sounds going to change and revolutionize everything? No its just another tool in a really great toolkit we have these days.

I thank my friend Oisin Grehan (http://nivot.org ) for inspiring this as I don’t think I’ve used sound in debugging since the my  Demoscene  days when I was coding MOD players. Mod Players . So naturaller the tracker syntax has inspired me a bit.

 

So how does this work. Well work out the steps you are listening to, and maybe things you particularly want to catch, play the notes over and over a few times so you know mentality what to expect, then just start running you code, and listen to what actually IS happening, and then maybe at certain times you might make a certain note MEAN something (like middle  C  for TRUE function result and middle A for FALSE), but I typically just use notices to indicate execution flow, and if I want to know some value, I ask windows Speech API to help me out.

function Play-Note([string]$note,[int] $duration = 5)
{
  if (!($note -match (\d+))) { $note+=4 };[void]($note -match ([A-G#]{1,2})(\d+))
  [console]::Beep((440 * [math]::Pow([math]::pow(2,(1/12)),
    (([int] $matches[2]) - 4)* 12 + $( switch($matches[1])
  {  A  { 0 }  A# { 1 }  Bb { 1 }  B  { 2 }  C  { 3 }  C# { 4 }  Db { 4 }
     D  { 5 }  D# { 6 }  Eb { 6 }  E  { 7 }  F  { 8 }  F# { 9 }  Gb { 9 }
     G  { 10 } G# { 11 } Ab { 11 }
  }))),$duration * 100 )
}

So two things. I’ve based the notes on common tracker syntax including the octave. so middle A is A4 , then you have things like F#3 etc. however if as with everything PowerShell we want pithy, so if you don’t put an octave number then it will be 4 by default.

play-note C ; play-note Eb ; play-note “C#”

also i included a duration, so if the want the note to be anything but the default half a second put that as a parameter

play-note Eb4 20

and you’ll get an E flat for 2 whole seconds. You may ask why I don’t use seconds or milliseconds, because those are our official measurements in this 1,000,000 3 grouped western world. Well the reason is pithiness (something i love in my scripts, but aren’t good about when it comes to blogging). Typically i want a note between say 300 milliseconds and 5 seconds. so if i used milliseconds i’d always have to have those 2 or 3 extra zeros, while if i used seconds I’d be doing things like 0.4 and 1.5. If Japanese and Chinese we often communicate number differently, and group them differently. so there is a word for 10,000 , so to say 15 thousand you may say 1 (character for 10,000) 5.

So what about voice. You can use a very simple function using the SAPI COM objects such as:

function Speak-words ([string]$words,[bool]$pause = $true)
{   $flag = 1
    if ($pause) {$flag = 2}
    $voice = new-Object -com SAPI.spvoice
    $voice.speak($words, [int] $flag)
    # 2 means wait until speaking is finished to continue
}

Typically though i don’t create a $voice everytime , but kept a reference somewhere , like in my V2 module. Others however have done more impressive things with the voice in powershell ( http://poshcode.org/667 | http://huddledmasses.org/powershell-speaks/ ).

So now you may want a way to play more notes and speak more words together, in a pithy line. you can just use play-notes

function Play-Notes
{
$defaultduration = 5;if($args[0] -is [int]) {$defaultduration = $args[0]}
for($i = 0;$i -lt $args.length;$i++)
 {    $duration = $defaultduration
    if ($i -lt $args.length-1) { if ($args[$i+1] -is [int]) {$duration = $args[$i+1] } }
    if ($args[$i] -is [string]) {
        if ($args[$i].startswith(!)) { speak-words $args[$i].replace(!,) } else
        { play-note $args[$i] $duration }
        }
 }
}

And with this you can simply pass in a list of notes, which will be played with a default duration, unless you choose to specify a duration after the note.

play-notes A B C 20 E 10 B 10

play-notes F5 2 E5 2 D5 2 C5 2 B5 2 A5 2 G 4 F5 2 E5 2 D5 2 C5 2 B5 2 A5 2 G 4

If you want to change the default duration to reduce the redundancy above, just put that number as the first parameter

play-notes 2 F5 E5 D5 C5 B5 A5 G 4 F5 E5 D5 C5 B5 A5 G 4

and finally if you want some speaking in there, just have your string start with an exclamation mark!

play-notes !here we go now Db5 Db Eb Gb Ab C# 6 !YEAH

But of course most of the time you will have speaking, you’ll want it to say something you don’t know like a variable or the results of a formula.

play-notes A!$([datetime]::now) C

So you can get all these functions together at poshcode. ( Script | Download )  . We’ll time will tell if this will stay a habit of mine or be a fad. Additionally if you wanted to take this seriously you’d probably want to use something better than console.beep , maybe you’d use a full on MOD engine like http://fmod.org/ and having it running iringn a different process, and have a very cpu lightweight IPC going on, and different threads could play different channels, so between threads you could debug via Chords if you really have the ear for it.

WARNING, NOT A CUBICLE FRIENDLY DEBUGGING TECHNIQUE WITHOUT HEADPHONES UNLESS YOU WANT YOUR COLLEGUES TO DEBUG YOU.

Enjoy,

Karl

Posted in Powershell, pscom | No Comments »

PowerSMUG – Syncronize folders on your machine with Smugmug.

January 12th, 2009 by Karl

In ShellTools most of you were familiar with only Tobias Weltner and Myself, however behind the scenes was a critical cofounder – Eddie Hadjes. Below is a script Eddie wrote a few months back to synchronize data between your local folders and the excellent SmugMug web album service. You can always get the latest version and info on our wiki.

Script @ PoshCode: View Script | Download

Share on Facebook

 

—-

# PowerSmug photo sync script v1.0
# This PowerShell script will syncronize a folder of images with a users SmugMug account
# Please set the appropriate variables in the User Defined Variables region
# For more information visit http://shelltools.wik.is/Other_Projects/PowerSmug
#
# Images are uploaded to a gallery with the same name as the folder they are contained in.
# All folders below the Photo Directory path and the images they contain will be uploaded to SmugMug
# Folders that end in _ are ignored, so if you don’t want to sync a folder with SmugMug, just add an underscore at the end
#
# Copyright 2008 Shell Tools, LLC

#Region User Defined Variables

$ApiKey = uVUvCbXP3f6MgO9wIRJn21YCIgEidVly
$EmailAddress = [SmugMug Email]
$Password = [SmugMug Password]
$PhotoDirectory = [Path To Photos]
$filesToInclude = @(*.jpg,*.png,*.tif,*.cr2)
$worldSearchable = 1
$smugSearchable = 1

# Professional Accounts Only
$watermark=0
$watermarkId=0

#Email Variables
$smtpServer = [SMTP Server]
$smtpUser = [SMTP User]
$smtpPassword = [SMTP Password]
$smtpFrom = PowerSmug@shelltools.net

#EndRegion

#Region Global Variables

$baseUrl = http://api.smugmug.com/hack/rest/1.2.0/
$userAgent = PowerSmug v1.0
$logFile = PowerSmug.log
$script:SessionId = $null
$script:startStringState = $false
$script:valueState = $false
$script:arrayState = $false
$script:saveArrayState = $false
$script:datFileDirty = $false
$script:datFile = @()
$script:albumList = $null
$script:imageList = $null

#EndRegion

#region Helper Functions

function SendEmail([string] $to, [string] $subject, [string] $msg)
{
    $smtpMail = new-Object System.Net.Mail.SmtpClient $smtpServer
    $smtpMail.DeliveryMethod = [System.Net.Mail.SmtpDeliveryMethod]Network
    $smtpMail.Credentials = new-Object System.Net.NetworkCredential $smtpUser, $smtpPassword

    $smtpMail.Send($smtpFrom, $to, $subject, $msg)
}

function Get-MD5([System.IO.FileInfo] $file = $(Throw Usage: Get-MD5 [System.IO.FileInfo]))
{
    # This Get-MD5 function sourced from:
    # http://blogs.msdn.com/powershell/archive/2006/04/25/583225.aspx
    $stream = $null;
    $cryptoServiceProvider = [System.Security.Cryptography.MD5CryptoServiceProvider];
    $hashAlgorithm = new-object $cryptoServiceProvider
    $stream = $file.OpenRead();
    $hashByteArray = $hashAlgorithm.ComputeHash($stream);
    $stream.Close();

    ## We have to be sure that we close the file stream if any exceptions are thrown.
    trap
    {
        if ($stream -ne $null) { $stream.Close(); }
        break;
    }

    # Convert the MD5 hash to Hex
    $hashByteArray | foreach { $result += $_.ToString(X2) }

    return $result
}

function SendWebRequest([string]$method, $queryParams, $ssl = $false)
{
    $url = $baseUrl
    if ($ssl -eq $true) {
        $url = $url.Replace(http, https)
    }

    $url += ?method=$method

    foreach ($key in $queryParams.Keys) {
        $url += &$key= + $queryParams[$key]
    }

    $wc = New-Object Net.WebClient

    $wc.Headers.Add(user-agent, $userAgent)
    [xml]$webResult = $wc.DownloadString($url)

    CheckResponseForError $webResult

    return $webResult.rsp
}

# Adds image information to our data file
function AddFile ([System.IO.FileInfo]$file, $smugId) { 

    $a = 1 | Select-Object Name, SmugId, LastModifiedTime;
    $a.Name = $file.Name
    $a.SmugId = $smugId
    $a.LastModifiedTime = $file.LastWriteTimeUtc.ToString()

    $script:datFile += $a
#    $script:datFileDirty = $true

    #we are now saving the file after each upload to prevent duplicates when there is a failure
    AppendDataFile $datFilePath $a
}

# writes the data file to the local directory
# each directory will have a data file with information for images contained in it
function SaveDataFile($filePath, $clearFile=$true)
{
    if ($script:datFileDirty -eq $false) { $script:datFile = @(); return }

    [System.IO.File]::Delete($filePath)

    $sortedFile = $script:datFile | sort-Object -property Name
    $sortedFile | Export-Csv $filePath 

    $script:datFileDirty = $false

    # mark the file as hidden
    ls $filePath | foreach { $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden }

    if ($clearFile -eq $true) {
        # clear the variable
        $script:datFile = @()
    }
}

# appends a new row to the data file
function AppendDataFile($filePath, $record)
{
    if (test-Path $datFilePath) {
        $newLine = [System.String]::Format({0},{1},”{2}”, $a.Name, $a.SmugId, $a.LastModifiedTime)
        Add-Content $filePath $newLine
    }
    else
    {
        $script:datFileDirty = $true
        SaveDataFile $filePath $false
    }
}

# ensure our web request was successful
function CheckResponseForError($xmlResponse)
{
    if ($xmlResponse.rsp.stat -eq fail)    {
        throw $webResult.rsp.err.msg
    }
}

#endregion

#region Login/Logout

function Login()
{
    if ($script:SessionId -ne $null) { return }

    $ht = @{APIKey=$ApiKey;EmailAddress=$EmailAddress;Password=$Password}
    $loginResult = SendWebRequest smugmug.login.withPassword $ht $true

    if ($loginResult.stat -eq ok) {
        $script:SessionId = $loginResult.Login.Session.id
    }
    else {
        Throw Error on login:  + $loginResult.Message
    }
}

function Logout()
{
    if ($script:SessionId -eq $null) { return }

    $ht = @{SessionID=$script:SessionId}
    $logoutResult = SendWebRequest smugmug.logout $ht
}

#endregion

#region Images

# this method is only needed if we cannot find the PowerSmug.dat file.
# preventing us from uploading duplicate images
function GetImage($name, $album)
{
    Login 

    $image = $script:imageList.Images | Where-Object { $_.FileName -eq $name }
    if ($image -ne $null) { return $image }

    # we are using heavy because we need the file name
    $ht = @{SessionID=$script:SessionId;AlbumID=$album.id;Heavy=1;AlbumKey=$album.Key}
    [xml]$script:imageList = SendWebRequest smugmug.images.get $ht

    return $script:imageList.Images.Image | Where-Object { $_.FileName -eq $name }
}

function UploadFile($file, $albumName)
{
    $album = GetAlbum $albumName
    # $image = GetImage $file.Name $album    
    # if ($image -ne $null) { return $image.id }

    $url = http://upload.smugmug.com/ + $file.Name

    $wc = New-Object Net.WebClient

    $hash = Get-MD5 $file

    $wc.Headers.Add(user-agent, $userAgent)
    $wc.Headers.Add(ContentMd5, $hash)
    $wc.Headers.Add(X-Smug-FileName, $file.Name)
    $wc.Headers.Add(X-Smug-AlbumID, $album.id)
    $wc.Headers.Add(X-Smug-SessionID, $script:SessionId)
    $wc.Headers.Add(X-Smug-Version, 1.2.0)
    $wc.Headers.Add(X-Smug-ResponseType, REST)
    $webResult = $wc.UploadFile($url, PUT, $file.FullName)
    [xml]$webResult = [System.Text.Encoding]::ASCII.GetString($webResult)

    CheckResponseForError $webResult

    return $webResult.rsp.Image.id
}

function UploadExistingFile($file, $id)
{
    Login 

    $url = http://upload.smugmug.com/ + $file.Name

    $wc = New-Object Net.WebClient

    $hash = Get-MD5 $file

    $wc.Headers.Add(user-agent, $userAgent)
    $wc.Headers.Add(ContentMd5, $hash)
    $wc.Headers.Add(X-Smug-FileName, $file.Name)
    $wc.Headers.Add(X-Smug-ImageID, $id)
    $wc.Headers.Add(X-Smug-SessionID, $script:SessionId)
    $wc.Headers.Add(X-Smug-Version, 1.2.0)
    $wc.Headers.Add(X-Smug-ResponseType, REST)
    $webResult = $wc.UploadFile($url, PUT, $file.FullName)

    [xml]$webResult = [System.Text.Encoding]::ASCII.GetString($webResult)

    CheckResponseForError $webResult

    return $webResult.rsp
}

#endregion

#region Album

function GetAlbumList($forceRefresh=$false) {
    Login

    if ($forceRefresh -eq $false) {
        if ($script:albumList -ne $null) { return }
    }

    $ht = @{SessionID=$script:SessionId}
    $script:albumList = SendWebRequest smugmug.albums.get $ht
}

function GetAlbum($name)
{
    #before we create an album ensure it doesn’t already exist
    GetAlbumList

    $album = $script:albumList.Albums.Album | Where-Object { $_.Title -eq $name }
    if ($album -ne $null) { return $album } 

    Write-Host Creating album: $name
    $ht = @{SessionID=$script:SessionId;Title=$name;CategoryID=0;Public=0;
    X2Larges=0;X3Larges=0;Originals=0;Watermarking=$watermark;
    WatermarkID=$watermarkId;WorldSearchable=$worldSearchable;SmugSearchable=$smugSearchable}
    $result = SendWebRequest smugmug.albums.create $ht

    # be sure we refresh the album list after creation
    GetAlbumList $true

    return $result.Album
}

#endregion

#region Process File

function ProcessFile([System.IO.FileInfo]$file, $albumName)
{
    $photoObject = $script:datFile | Where-Object { $_.Name -eq $file.Name }

    if ($photoObject -ne $null) {
        if ($photoObject.LastModifiedTime -ne $file.LastWriteTimeUtc.ToString()) {
            # file has been modified, so re-upload the file
            write-Host Updating existing file:  $file.FullName
            UploadExistingFile $file $photoObject.SmugId
            $photoObject.LastModifiedTime = $file.LastWriteTimeUtc.ToString()

            # mark the dat file as dirty so it will be saved after processing this folder
            $script:datFileDirty = $true
        }
    }
    else {
        #file doesn’t exist in local file, upload to SmugMug
        write-Host Uploading new file:  $file.FullName
        $id = UploadFile $file $albumName
        AddFile $file $id
    }
}

#endregion

#region Main Script

# this section will look through all sub-directories of $PhotoDirectory and upload the images to SmugMug
Get-ChildItem -recurse $PhotoDirectory | Where-Object { $_.Attributes -band [System.IO.FileAttributes]::Directory } | foreach {
    # don’t process folders that end in _
    if ($_.FullName.EndsWith(_) -eq $false) {
        $datFilePath = $_.FullName + \PowerSmug.dat
        if (test-Path $datFilePath) {
            # casting as array to ensure we have an array returned
            [array]$script:datFile = import-Csv $datFilePath
        }

        $albumName = $_.FullName.Remove(0, $PhotoDirectory.Length).Trim(\’)

        $path = $_.FullName + \*
        foreach ($file in get-ChildItem $path -include $filesToInclude) {
            ProcessFile $file $albumName
        }
        SaveDataFile $datFilePath
        $script:imageList = $null
    }
}

Logout

$date = Get-Date
Write-Host Script Completed: $date

trap [Exception] {
        $date = Get-Date
        $Msg = $date.ToString() +  ;  + $_.Exception.GetType().FullName +  ;  + $_.Exception.Message
        Add-Content $logFile $Msg
        SendEmail $EmailAddress PowerSmug Error $Msg
        break
    }

#endregion

—–

Posted in Powershell, pscom | No Comments »

« Previous Entries