Windows Azure Powershell Copying file to VM

Here is some code that I got from some powershell examples and modified. It works over a session created with New-PSSession. There's a cool wrapper for that also included below. Lastly, I needed to send a whole folder over so that's here too..

Some example usage for tying them together

# open remote session
$session = Get-Session -uri $uri -credentials $credential 
# copy installer to VM
Write-Verbose "Checking if file $installerDest needs to be uploaded"
Send-File -Source $installerSrc -Destination $installerDest -Session $session -onlyCopyNew $true



<#
.SYNOPSIS
  Returns a session given the URL 
.DESCRIPTION
  http://michaelcollier.wordpress.com/2013/06/23/using-remote-powershell-with-windows-azure-vms/
#>
function Get-Session($uri, $credentials)
{
    for($retry = 0; $retry -le 5; $retry++)
    {
      try
      {
        $session = New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credentials -Port $uri[0].Port -UseSSL
        if ($session -ne $null)
        {
            return $session
        }

        Write-Output "Unable to create a PowerShell session . . . sleeping and trying again in 30 seconds."
        Start-Sleep -Seconds 30
      }
      catch
      {
        Write-Output "Unable to create a PowerShell session . . . sleeping and trying again in 30 seconds."
        Start-Sleep -Seconds 30
      }
    }
}

<#
.SYNOPSIS
  Sends a file to a remote session.
  NOTE: will delete the destination before uploading
.EXAMPLE
  $remoteSession = New-PSSession -ConnectionUri $remoteWinRmUri.AbsoluteUri -Credential $credential
  Send-File -Source "c:\temp\myappdata.xml" -Destination "c:\temp\myappdata.xml" $remoteSession

  Copy the required files to the remote server 

    $remoteSession = New-PSSession -ConnectionUri $frontEndwinRmUri.AbsoluteUri -Credential $credential
    $sourcePath = "$PSScriptRoot\$remoteScriptFileName"
    $remoteScriptFilePath = "$remoteScriptsDirectory\$remoteScriptFileName"
    Send-File $sourcePath $remoteScriptFilePath $remoteSession

    $answerFileName = Split-Path -Leaf $WebPIApplicationAnswerFile
    $answerFilePath = "$remoteScriptsDirectory\$answerFileName"
    Send-File $WebPIApplicationAnswerFile $answerFilePath $remoteSession
    Remove-PSSession -InstanceId $remoteSession.InstanceId
#>
function Send-File
{
    param (

        ## The path on the local computer
        [Parameter(Mandatory = $true)]
        [string]
        $Source,

        ## The target path on the remote computer
        [Parameter(Mandatory = $true)]
        [string]
        $Destination,

        ## The session that represents the remote computer
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.Runspaces.PSSession] 
        $Session,

        ## should we quit if file already exists?
        [bool]
        $onlyCopyNew = $false

        )

    $remoteScript =
    {
        param ($destination, $bytes)

        # Convert the destination path to a full filesystem path (to supportrelative paths)
        $Destination = $ExecutionContext.SessionState.`
        Path.GetUnresolvedProviderPathFromPSPath($Destination)

        # Write the content to the new file
        $file = [IO.File]::Open($Destination, "OpenOrCreate")
        $null = $file.Seek(0, "End")
        $null = $file.Write($bytes, 0, $bytes.Length)
        $file.Close()
    }

    # Get the source file, and then start reading its content
    $sourceFile = Get-Item $Source

    # Delete the previously-existing file if it exists
    $abort = Invoke-Command -Session $Session {
        param ([String] $dest, [bool]$onlyCopyNew)

        if (Test-Path $dest) 
        { 
            if ($onlyCopyNew -eq $true)
            {
                return $true
            }

            Remove-Item $dest
        }

        $destinationDirectory = Split-Path -Path $dest -Parent
         if (!(Test-Path $destinationDirectory))
        {
            New-Item -ItemType Directory -Force -Path $destinationDirectory 
        }

        return $false
    } -ArgumentList $Destination, $onlyCopyNew

    if ($abort -eq $true)
    {
        Write-Host 'Ignored file transfer - already exists'
        return
    }

    # Now break it into chunks to stream
    Write-Progress -Activity "Sending $Source" -Status "Preparing file"
    $streamSize = 1MB
    $position = 0
    $rawBytes = New-Object byte[] $streamSize
    $file = [IO.File]::OpenRead($sourceFile.FullName)
    while (($read = $file.Read($rawBytes, 0, $streamSize)) -gt 0)
    {
        Write-Progress -Activity "Writing $Destination" -Status "Sending file" `
            -PercentComplete ($position / $sourceFile.Length * 100)

        # Ensure that our array is the same size as what we read from disk
        if ($read -ne $rawBytes.Length)
        {
            [Array]::Resize( [ref] $rawBytes, $read)
        }

        # And send that array to the remote system
        Invoke-Command -Session $session $remoteScript -ArgumentList $destination, $rawBytes

        # Ensure that our array is the same size as what we read from disk
        if ($rawBytes.Length -ne $streamSize)
        {
            [Array]::Resize( [ref] $rawBytes, $streamSize)
        }
        [GC]::Collect()
        $position += $read
    }

    $file.Close()

    # Show the result
    Invoke-Command -Session $session { Get-Item $args[0] } -ArgumentList $Destination
}

<#
.SYNOPSIS
  Sends all files in a folder to a remote session.
  NOTE: will delete any destination files before uploading
.EXAMPLE
  $remoteSession = New-PSSession -ConnectionUri $remoteWinRmUri.AbsoluteUri -Credential $credential
  Send-Folder -Source 'c:\temp\' -Destination 'c:\temp\' $remoteSession
#>
function Send-Folder 
{
    param (
        ## The path on the local computer
        [Parameter(Mandatory = $true)]
        [string]
        $Source,

        ## The target path on the remote computer
        [Parameter(Mandatory = $true)]
        [string]
        $Destination,

        ## The session that represents the remote computer
     #   [Parameter(Mandatory = $true)]
        [System.Management.Automation.Runspaces.PSSession] 
        $Session,

        ## should we quit if files already exist?
        [bool]
        $onlyCopyNew = $false
    )

    foreach ($item in Get-ChildItem $Source)
    {
        if (Test-Path $item.FullName -PathType Container) {
            Send-Folder $item.FullName "$Destination\$item" $Session $onlyCopyNew
        } else {
            Send-File -Source $item.FullName -Destination "$destination\$item" -Session $Session -onlyCopyNew $onlyCopyNew
        }
    }
}

Here is ano ther approach that I documented here. It involves

  1. Creating and mounting an empty local VHD.
  2. Copying your files to the new VHD and dismount it.
  3. Copy the VHD to azure blob storage
  4. Attach that VHD to your VM.

Here is an example:

#Create and mount a new local VHD
$volume = new-vhd -Path test.vhd -SizeBytes 50MB | `
  Mount-VHD -PassThru | `
  Initialize-Disk -PartitionStyle mbr -Confirm:$false -PassThru | `
  New-Partition -UseMaximumSize -AssignDriveLetter -MbrType IFS | `
  Format-Volume -NewFileSystemLabel "VHD" -Confirm:$false

#Copy my files  
Copy-Item C:\dev\boxstarter "$($volume.DriveLetter):\" -Recurse
Dismount-VHD test.vhd

#upload the Vhd to azure
Add-AzureVhd -Destination http://mystorageacct.blob.core.windows.net/vhdstore/test.vhd `
  -LocalFilePath test.vhd

#mount the VHD to my VM
Get-AzureVM MyCloudService MyVMName | `
  Add-AzureDataDisk -ImportFrom `
  -MediaLocation "http://mystorageacct.blob.core.windows.net/vhdstore/test.vhd" `
  -DiskLabel "boxstarter" -LUN 0 | `
  Update-AzureVM