# encoding: ascii
# api: powershell
# title: PowerSmug - Sync Smugmug
# description: Syncronize local folders with Smugmug using this powershell script.
# version: 1.0
# type: script
# author: Karl Prosser
# license: CC0
# function: Get-MD5
# x-poshcode-id: 796
# x-archived: 2016-08-09T21:49:05
# x-published: 2009-01-11T23:33:00
#
#
# 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
#
# 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