Summary: Microsoft PFE, Chris Wu, talks about creating powerful automation scenarios by using Windows PowerShell and SkyDrive.
Microsoft Scripting Guy, Ed Wilson, is here. Chris Wu is back with us today. I will let Chris dive right in…
On the Microsoft Download Center, you can download Live SDK v5.3, which contains APIs and a great deal of information about how to achieve common tasks. You can also find the information on the Live Connect documentation site. If you have stored a valid access token (by using wl.skydrive or wl.skydrive_update scope) in a variable (for example, $AccessToken), most of the tasks can be easily achieved with Windows PowerShell. For information about how to create a valid access token, read my previous post, Use PowerShell 3.0 to Get More Out of Windows Live.
A bit of background about the SkyDrive folder structure…
Each SkyDrive user has a unique ID, with a top-level folder usually named in the form "folder.<user id>". Files and folders also have unique IDs, which are composed of several parts including a keyword (such as "folder" or "file") and the owner’s unique ID. So, every file and folder in SkyDrive can be uniquely identified and individually accessed.
Browse SkyDrive folders
To access the current user’s top-level folder, simply invoke a Rest method. The JSON-formatted object that’s returned will show properties of the folder in a very familiar way:
$ApiUri = "https://apis.live.net/v5.0"
Invoke-RestMethod -Uri "$ApiUri/me/skydrive?access_token=$AccessToken"
There are some interesting properties such as ID, name, number of items, and permission. Among them, the upload_location property is actually the API URI through which we can enumerate or upload files. This example shows how to enumerate child items:
$Root = Invoke-RestMethod -Uri "$ApiUri/me/skydrive?access_token=$AccessToken"
$r = Invoke-RestMethod -Uri "$($Root.upload_location)?access_token=$AccessToken"
$r.data | ft Name, id, upload_location –AutoSize
This enables you to browse the hierarchy inside the SkyDrive.
Access SkyDrive folders through friendly names
Folders such as Documents, Pictures, and Public can be accessed through friendly names such as those shown in the following table:
Friendly Name | Folder |
camera_roll | The camera roll folder |
my_documents | The Documents folder |
my_photos | The Pictures folder |
public_documents | The Public folder |
To get the Documents folder’s property, simply call:
$Docs = Invoke-RestMethod -Uri "$ApiUri/me/skydrive/my_documents?access_token=$AccessToken"
And to enumerate child items in Documents, call:
Invoke-RestMethod -Uri "$ApiUri/me/skydrive/my_documents/files?access_token=$AccessToken"
Upload and download files
Before uploading files, we need to make sure that the access token that originally was requested includes wl.skydrive_update scope. We can upload files only with proper permissions. It’s also good to check the available space on SkyDrive. Here is how to query the information:
Invoke-RestMethod -Uri "$ApiUri/me/skydrive/quota?access_token=$AccessToken"
The following screenshot shows that 7 GB is available:
Uploading a file can be accomplished through a Post request or Put request. The Put method is preferred whenever it’s applicable, although a small caveat is that the target file name on SkyDrive has to be specified in the request. The following example uploads a Windows PowerShell script from c:\temp\myscript.ps1 to the root on SkyDrive:
$fn = "myscript.ps1"
Invoke-RestMethod -Uri "$ApiUri/me/skydrive/files/$fn`?access_token=$AccessToken" -Method Put -InFile "c:\temp\$fn"
We can use any folder’s upload_location property as the destination to upload a file. We have a $Docs variable from previous tests, so the following script snippet uploads a binary file to the Documents folder on SkyDrive:
$fn="test.zip"
Invoke-RestMethod -Uri "$($Docs.upload_location)/$fn`?access_token=$AccessToken" -Method Put -InFile "C:\temp\$fn"
If a file with exactly the same name already exists in the target folder, it will be replaced with new content.
To download a file, we basically request the content of a file. Assuming a file’s ID has been determined through folder browsing or from the object that is returned when uploading it, the following command downloads the content of a text-based file:
$fileid = "file.6e8e4812f1211e8a.6E8E4812F1211E8A!205"
Invoke-RestMethod -Uri "$ApiUri/$fileid/content?access_token=$AccessToken"
The previous script snippet downloads a script and saves the code into a variable. This works great if the script is an ANSI-encoded text file. However, binary files and text files encoded in UTF8 may suffer data distortion due to the cmdlet’s attempt to parse the content. Saving the content directly into a local file can solve the problem:
Invoke-RestMethod -Uri "$ApiUri/$fileid/content?access_token=$AccessToken" -OutFile c:\temp\test2.zip
Other users’ files
So far, we have been accessing the folders and files of currently signed-in users. But the same syntax applies to folders and files for other user’s in SkyDrive that have been shared with you by the owner. The "me" portion in the URI used earlier is simply an alias for the current user, and it can be replaced with another user’s ID to browse those folders.
To get a current user’s ID, send a web request to "$ApiUri/me":
$UserID = (Invoke-RestMethod -Uri "$ApiUri/me?access_token=$AccessToken").id
With the user ID figured out, the following command can return all items shared by the user in the Public folder (assuming you have permission to access them):
(Invoke-RestMethod -Uri "$ApiUri/$UserID/skydrive/public_documents?access_token=$AccessToken").data | ft name, id -autosize
To wrap things up, I use the following script snippet to synchronize a SkyDrive folder (one-way sync) to a local disk:
# Function to sync all files from one SkyDrive Folder to a local Target
function Sync-SkyDriveFolder {
Param(
[PSObject]$Folder,
[String]$Target,
[Switch]$Recurse
)
(Invoke-RestMethod -Uri "$($Folder.upload_location)?access_token=$AccessToken").data | % {
$Path = "$Target\$($_.Name)"
if($_.type -eq "file") {
if(!(Test-Path $Path) -or $_.size -ne (Get-Item $Path).Length) {
Invoke-RestMethod -Uri "$($_.upload_location)?access_token=$AccessToken" -OutFile $Path
}
}
elseif ($Recurse) {
if(!(Test-Path $Path)) { New-Item -Path $Path -ItemType Directory | Out-Null }
Sync-SkyDriveFolder -Folder $_ -Target $Path -Recurse
}
}
}
$ApiUri = "https://apis.live.net/v5.0"
$Source = "Documents\Tools" # Relative path of the SkyDrive folder
$Destination = "C:\Tools" # Local folder to save files to
# Find the object representing the source folder on SkyDrive
$Folder = Invoke-RestMethod -Uri "$ApiUri/me/skydrive?access_token=$AccessToken"
foreach($s in $Source -split '[\\\/]') {
if ($s = $s.Trim()) {
$Folder = (Invoke-RestMethod -Uri "$($Folder.upload_location)?access_token=$AccessToken").data | ? { $_.name -eq $s }
if(! $Folder) { return }
}
}
# Start download recursively
Sync-SkyDriveFolder -Folder $Folder -Target $Destination -Recurse
Apparently, the script is pretty rough at this point. It checks identical files based only on size. It doesn’t confirm the validity of access tokens, which may lead to failure for a long-running task, and there is no error handling. But still, it serves its purpose pretty well. With further polish, it can even be a replacement desktop SkyDrive app for my Surface RT (or any computer running Windows RT).
~Chris
Thanks again for sharing, Chris. Join us tomorrow for more of Chris Wu and WindowsPowerShell.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy