Copy-Disk.ps1
|
<# .Synopsis Upload a disk to the cloud. .Description Upload a disk to a cloud platform. The currently supported platforms are Azure, AWS and GCP. #> Function Copy-Disk { [CmdletBinding(DefaultParameterSetName = 'cmd')] Param( [Obsolete("Use command-line arguments specifying one of -ToAzure, -ToAWS or -ToGCP.")] [Parameter(Mandatory = $True, ParameterSetName = 'file')] [string] $ConfigJsonFile, [Obsolete("Use one of -ToAzure, -ToAWS or -ToGCP.")] [Parameter(Mandatory = $True, ParameterSetName = 'cmd')] [string] $CloudPlatform, [Parameter(ParameterSetName = 'cmd')] [string] $CustomerId, [Parameter(ParameterSetName = 'cmd')] [string] $SmbHost, [Parameter(ParameterSetName = 'cmd')] [string] $SmbPort = $null, [Parameter(Mandatory = $True, ParameterSetName = 'cmd')] [string] $SmbShare, [Parameter(ParameterSetName = 'cmd')] [string] $SmbPath, [Parameter(Mandatory = $True, ParameterSetName = 'cmd')] [string] $SmbDiskName, [Parameter(ParameterSetName = 'cmd')] [string] $SmbDiskFormat = "VhdDiskFormat", [Parameter(Mandatory = $True, ParameterSetName = 'cmd')] [string] $SmbUserDomain, [Parameter(Mandatory = $True, ParameterSetName = 'cmd')] [string] $SmbUserName, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [pscredential] $SmbCred, [Parameter()] [int] $UploadTimeout = 36000, [Parameter()] [int] $Threads, [Parameter()] [switch] $Install, [Parameter()] [string] $LogFile, [Parameter()] [switch] $OverwriteLog, [Parameter()] [switch] $Force, # # AWS # [Parameter(Mandatory = $True, ParameterSetName = 'aws')] [switch] $ToAWS, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'aws')] [string] $Description, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'aws')] [HashTable] $Tags, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(ParameterSetName = 'aws')] [string] $AwsRegion, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(ParameterSetName = 'aws')] [string] $AwsProfileName, # # Azure # [Parameter(Mandatory = $True, ParameterSetName = 'azure')] [switch] $ToAzure, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'azure')] [string] $AzureSubscriptionId, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'azure')] [string] $AzureLocation, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'azure')] [string] $TargetResourceGroup, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'azure')] [string] $AzureStorageType = "Premium_LRS", [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(ParameterSetName = 'azure')] [string] $AzureClientId, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(ParameterSetName = 'azure')] [string] $AzureSecret, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(ParameterSetName = 'azure')] [string] $AzureTenantId, # # GCP # [Parameter(Mandatory = $True, ParameterSetName = 'gcp')] [switch] $ToGCP, [Parameter(ParameterSetName = 'cmd')] [Parameter(ParameterSetName = 'file')] [Parameter(Mandatory = $True, ParameterSetName = 'gcp')] [string] $GcpServiceAccountKeyFile, [Parameter(ParameterSetName = 'cmd')] [Parameter(Mandatory = $True, ParameterSetName = 'azure')] [Parameter(Mandatory = $True, ParameterSetName = 'gcp')] [string] $CloudDiskName, [Parameter(Mandatory = $True, ParameterSetName = 'azure')] [Parameter(Mandatory = $True, ParameterSetName = 'gcp')] [Parameter(Mandatory = $True, ParameterSetName = 'aws')] [string] $Path ) Begin { InitUploadLog $LogFile $OverwriteLog if ($PSCmdlet.ParameterSetName -eq 'file') { Log "Loading configuration from $ConfigJsonFile" $False $configData = Get-Content -Raw -Path $ConfigJsonFile | ConvertFrom-Json Log "Configuration: $configData" $False $CustomerId = $configData.CustomerId $CloudPlatform = $configData.CloudPlatform $SmbHost = $configData.UploadSmb.Host $SmbPort = $configData.UploadSmb.Port $SmbShare = $configData.UploadSmb.Share $SmbPath = $configData.UploadSmb.Path $SmbDiskName = $configData.UploadSmb.DiskName $SmbUserDomain = $configData.UploadSmb.UserDomain $SmbUserName = $configData.UploadSmb.UserName $AzureSubscriptionId = $configData.AzureSubscriptionId $AzureLocation = $configData.AzureLocation $TargetResourceGroup = $configData.TargetResourceGroup $CloudDiskName = $configData.CloudDiskName if (-not [String]::IsNullOrWhiteSpace($configData.UploadSmb.DiskFormat)) { $SmbDiskFormat = $configData.UploadSmb.DiskFormat } if ($null -ne $configData.UploadTimeout -and $configData.UploadTimeout -gt 0) { $UploadTimeout = [int]$configData.UploadTimeout } if (-not [String]::IsNullOrWhiteSpace($configData.AzureStorageType)) { $AzureStorageType = $configData.AzureStorageType } if ($Threads -le 0 -and -not [String]::IsNullOrWhiteSpace($configData.Threads)) { $Threads = [int]$configData.Threads } if ([String]::IsNullOrWhiteSpace($GcpServiceAccountKeyFile)) { $GcpServiceAccountKeyFile = $configData.GcpServiceAccountKeyFile } } if (($PSCmdlet.ParameterSetName -eq 'file') -or ($PSCmdlet.ParameterSetName -eq 'cmd')) { $smbConfig = InitSmbConfig $SmbHost $SmbPort $SmbShare $SmbPath $SmbUserDomain $SmbUserName $SmbDiskName $SmbDiskFormat if ($null -eq $SmbCred) { Log "Generating SMB credential using username and password" $False $password = Read-Host -assecurestring "SMB user password" $smbConfig.SmbCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $smbConfig.UserAndDomain, $password } else { Log "SMB credential given as input" $False $smbConfig.SmbCred = $SmbCred } } } Process { try { if ($ToAzure -or ($CloudPlatform -eq "Azure")) { Log "Loading required modules" $False LoadModules @('Az.Accounts', 'Az.Compute') $Install AuthAzure $AzureClientId $AzureSecret $AzureTenantId $AzureSubscriptionId if ($Force) { CleanUpAzureDisk $CloudDiskName $TargetResourceGroup } elseif ($null -ne (GetAzureDisk $CloudDiskName $TargetResourceGroup)) { $msg = "Managed disk '$CloudDiskName' aleady exists in resource group $TargetResourceGroup. Consider using the -Force option" ThrowError ([UploaderException]::new($msg)) } if ($ToAzure) { $fileSize = GetVhdSize $Path } else { $fileSize = GetVhdSizeOnSmbShare $smbConfig } $diskUrlWithSas = CreateManagedDisk $fileSize $UploadTimeout $AzureStorageType $AzureLocation $TargetResourceGroup $CloudDiskName $InformationPreference = "Continue" try { if ($ToAzure) { UploadToAzure $diskUrlWithSas $Path $Threads $TargetResourceGroup $CloudDiskName } else { UploadFromSmbToAzure $diskUrlWithSas $smbConfig $Threads $TargetResourceGroup $CloudDiskName } } catch { ThrowError ([UploaderException]::new("Failed to copy disk to Azure", $_.Exception)) } } if ($ToAWS -or ($CloudPlatform -eq "Aws")) { $InformationPreference = "Continue" try { if ($ToAWS) { $snapshotId = UploadToAws $Path $AwsProfileName $AwsRegion $Description $Tags $Threads } else { $snapshotId = UploadFromSmbToAws $smbConfig $AwsProfileName $AwsRegion $Description $Tags $Threads } } catch { ThrowError ([UploaderException]::new("Failed to copy disk to AWS", $_.Exception)) } return $snapshotId } if ($ToGCP -or ($CloudPlatform -eq "Gcp")) { # Temporary solution to redirecting 1.48 assemblies that are compile time referenced in the Google.Api.Gax.Rest assemblies # to the 1.49 assemblies that the other Google assemblies reference # This is the powershell way to accomplish Binding Redirects, that are typically done in the configuration file. $modulePath = (Get-Item (Get-Module -Name Citrix.Image.Uploader).Path).DirectoryName $GoogleApis = [reflection.assembly]::LoadFrom($modulePath + "\bin\netstandard2.0\Google.Apis.dll") $GoogleApisCore = [reflection.assembly]::LoadFrom($modulePath + "\bin\netstandard2.0\Google.Apis.Core.dll") $GoogleApisAuth = [reflection.assembly]::LoadFrom($modulePath + "\bin\netstandard2.0\Google.Apis.Auth.dll") $OnAssemblyResolve = [System.ResolveEventHandler] { param($s, $e) Log "Resolving assembly '$($e.Name)'" $False if (($e.Name.StartsWith("Google.Apis.Core, Version=1.49.0.0")) -or ($e.Name.StartsWith("Google.Apis.Auth, Version=1.49.0.0")) -or ($e.Name.StartsWith("Google.Apis, Version=1.49.0.0"))) { Log ("This workaround may no longer be necessary. The Google assemblies may now be referencing the correct assemblies. " + "Try removing this event handler (OnAssemblyResolve) and try again.") $False } if ($e.Name -eq "Google.Apis.Core, Version=1.48.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab") { Log ("Forcing the Assembly '$($e.Name)' to be '$($GoogleApisCore.GetName().Name), " + "Version=$($GoogleApisCore.GetName().Version)'") $False return $GoogleApisCore } if ($e.Name -eq "Google.Apis.Auth, Version=1.48.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab") { Log ("Forcing the Assembly '$($e.Name)' to be '$($GoogleApisAuth.GetName().Name), " + "Version=$($GoogleApisAuth.GetName().Version)'") $False return $GoogleApisAuth } if ($e.Name -eq "Google.Apis, Version=1.48.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab") { Log "Forcing the Assembly '$($e.Name)' to be '$($GoogleApis.GetName().Name), Version=$($GoogleApis.GetName().Version)'" $False return $GoogleApis } foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies()) { if ($a.FullName -eq $e.Name) { return $a } } return $null } Log "Registering AssemblyResolve event handling" $False [System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve) if ($Force) { CleanUpGcpDisk $GcpServiceAccountKeyFile $CloudDiskName } $InformationPreference = "Continue" try { if ($ToGCP) { UploadToGcp $CloudDiskName $Path $GcpServiceAccountKeyFile } else { UploadFromSmbToGcp $CloudDiskName $GcpServiceAccountKeyFile $smbConfig } } catch [System.Reflection.ReflectionTypeLoadException] { Log "Message: $($_.Exception.Message)" $False Log "StackTrace: $($_.Exception.StackTrace)" $False Log "LoaderExceptions: $($_.Exception.LoaderExceptions)" $False try { Log "Redirected Google SDK package version and retry uploading process" if ($ToGCP) { UploadToGcp $CloudDiskName $Path $GcpServiceAccountKeyFile } else { UploadFromSmbToGcp $CloudDiskName $GcpServiceAccountKeyFile $smbConfig } } catch { ThrowError ([UploaderException]::new("Failed to copy disk to Google Cloud", $_.Exception)) } } catch { ThrowError ([UploaderException]::new("Failed to copy disk to Google Cloud", $_.Exception)) } } } catch [UploaderException] { LogIfSslError $_ $PSCmdlet.ThrowTerminatingError($_) } catch { Log $_ LogIfSslError $_ $PSCmdlet.ThrowTerminatingError($_) } } } Function InitSmbConfig([string]$smbHost, [string]$smbPort, [string]$smbShare, [string]$smbPath, [string]$smbUserDomain, [string]$smbUserName, [string]$smbDiskName, [string]$smbDiskFormat) { $DISK_FORMATS = @{ VhdDiskFormat = "vhd" VhdxDiskFormat = "vhdx" VmdkDiskFormat = "vmdk" VmdkSparseDiskFormat = "vmdk" QCow2DiskFormat = "qcow" RawDiskFormat = "raw" } $smbConfig = @{} $smbConfig.DiskExtension = $DISK_FORMATS[$smbDiskFormat] $smbConfig.UserAndDomain = "$($smbUserDomain)\$($smbUserName)" if ($smbPort) { $smbConfig.ShareUnc = "\\$($smbHost):$($smbPort)\$($smbShare)" } else { $smbConfig.ShareUnc = "\\$($smbHost)\$($smbShare)" } if ($smbPath) { $smbConfig.ExportFilePath = Join-Path -Path $smbPath -ChildPath "$($smbDiskName).$($smbConfig.DiskExtension)" } else { $smbConfig.ExportFilePath = "$($smbDiskName).$($smbConfig.DiskExtension)" } $smbConfig.FileOnShare = Join-Path -Path $smbConfig.ShareUnc -ChildPath $smbConfig.ExportFilePath return $smbConfig } Function LoadModules([string[]]$modules, [bool]$installModule = $False) { foreach ($module in $modules) { if (Get-Module -ListAvailable -Name $module) { Log "Module $module exists" $False } else { if ($installModule) { Log "Installing $module" $False Install-Module -Name $module -Scope CurrentUser -AllowClobber -Force } else { $msg = "Module $module is missing. You can either install it manually or add -Install in the cmdlet to auto-install all " + "missing required modules" ThrowError ([UploaderException]::new($msg)) } } } } Function AuthAzure([string]$azureClientId, [string]$azureSecret, [string]$azureTenantId, [string]$azureSubscriptionId) { if ($azureClientId -And $azureSecret -And $azureTenantId) { Log "Authenticating to Azure as service principal $azureClientId in subscription $azureSubscriptionId" $secret = ConvertTo-SecureString $azureSecret -AsPlainText -Force $pscredential = New-Object -TypeName System.Management.Automation.PSCredential($azureClientId, $secret) $null = Connect-AzAccount -ServicePrincipal -Credential $pscredential -Tenant $azureTenantId -Subscription $azureSubscriptionId ` -ErrorAction Stop } else { $context = Get-AzContext if ($null -eq $context -or ($context.Subscription.Id -ne $azureSubscriptionId)) { Log "Authenticating to Azure interactively in subscription $azureSubscriptionId" $null = Connect-AzAccount -Subscription $azureSubscriptionId -ErrorAction Stop } else { Log "Already connected to subscription $azureSubscriptionId" $False } } } Function ErrorIsResourceNotFound([object]$err) { try { return $err[0].Exception.Message -like "*ResourceNotFound*" } catch { } return $False } Function GetAzureDisk([string]$cloudDiskName, [string]$targetResourceGroup) { $err = "" $disk = Get-AzDisk -ResourceGroupName $targetResourceGroup -DiskName $cloudDiskName -ErrorAction SilentlyContinue -ErrorVariable err if ($null -ne $disk) { return $disk } if (ErrorIsResourceNotFound $err) { return $null } $msg = "Failure trying to find managed disk '$cloudDiskName' in resource group $targetResourceGroup" ThrowError ([UploaderException]::new($msg, $err.Exception)) } Function CleanUpAzureDisk([string]$cloudDiskName, [string]$targetResourceGroup) { $disk = GetAzureDisk $cloudDiskName $targetResourceGroup if ($null -ne $disk) { Log "Deleting existing managed disk '$cloudDiskName' in resource group $targetResourceGroup" $err = "" $result = Remove-AzDisk -ResourceGroupName $targetResourceGroup -DiskName $disk.Name -Force -ErrorAction SilentlyContinue -ErrorVariable err if ($null -eq $result) { ThrowError ([UploaderException]::new("Failed to delete existing managed disk '$($disk.Name)'", $err.Exception)) } Log "Remove-AzDisk status $($result.status)" $False Log "Deleted managed disk '$($disk.Name)'" } } Function GetVhdSize([string]$path) { try { $fileSize = Get-VhdSize -File $path -RoundUp -IncludeFooterSize Log "VHD size for $path is $fileSize" return $fileSize } catch { ThrowError ([UploaderException]::new("Failed to get VHD size for $($path)", $_)) } } Function GetVhdSizeOnSmbShare([psobject]$smbConfig) { try { Log "Getting VHD size as $($smbConfig.UserAndDomain) for $($smbConfig.FileOnShare)" $False $getVhdSize = { param($SharePath, $Arguments) $fullPath = Join-Path -Path $SharePath -ChildPath $Arguments[0] return Get-VhdSize -File $fullPath -RoundUp -IncludeFooterSize } $fileSize = ExecuteOnSmbShare $getVhdSize $smbConfig.SmbCred $smbConfig.ShareUnc @($smbConfig.ExportFilePath) Log "VHD size for $($smbConfig.FileOnShare) is $fileSize" return $fileSize } catch { ThrowError ([UploaderException]::new("Failed to get VHD size for $($smbConfig.FileOnShare)", $_)) } } Function CreateManagedDisk([long]$sizeInBytes, [int]$uploadTimeout, [string]$azureStorageType, [string]$azureLocation, [string]$targetResourceGroup, [string]$cloudDiskName) { $sasExpiryDuration = $uploadTimeout $diskConfig = New-AzDiskConfig -AccountType $azureStorageType -Location $azureLocation -UploadSizeInBytes $sizeInBytes -CreateOption 'Upload' ` -OsType Windows -HyperVGeneration V2 Log "Creating managed disk '$cloudDiskName' with size $sizeInBytes bytes in resource group $targetResourceGroup location $azureLocation" $err = "" $disk = New-AzDisk -ResourceGroupName $targetResourceGroup -DiskName $cloudDiskName -Disk $diskConfig -ErrorAction SilentlyContinue ` -ErrorVariable err if ($null -eq $disk) { ThrowError ([UploaderException]::new("Failed to create managed disk '$cloudDiskName' in resource group $targetResourceGroup", $err.Exception)) } Log "Granting access to managed disk '$cloudDiskName' for $sasExpiryDuration seconds" $False $err = "" $access = Grant-AzDiskAccess -ResourceGroupName $targetResourceGroup -DiskName $cloudDiskName -DurationInSecond $sasExpiryDuration ` -Access 'Write' -ErrorAction SilentlyContinue -ErrorVariable err if ($null -eq $access) { ThrowError ([UploaderException]::new("Failed to create SAS for mananged disk '$cloudDiskName'", $err.Exception)) } $sas = $access.AccessSAS Log "Created managed disk '$cloudDiskName' with SAS $sas" $False return $sas } Function DoAzureUpload([ScriptBlock]$uploadScript, [string]$diskName, [int]$threads, [string]$targetResourceGroup, [string]$cloudDiskName) { Log ("Copying disk '$diskName' to managed disk '$cloudDiskName' " + $(if ($threads -le 0) {"(threads=default)"} else {"(threads=$threads)"})) $ex = $null try { & $uploadScript Log "Revoking Azure disk access" $False $err = "" $r = Revoke-AzDiskAccess -ResourceGroupName $targetResourceGroup -DiskName $cloudDiskName -ErrorAction SilentlyContinue -ErrorVariable err if ($null -eq $r) { ThrowError ([UploaderException]::new("Revoke-AzDiskAccess failed", $err.Exception)) $False } Log "Revoke-AzDiskAccess status '$($r.status)'" $False } catch { $ex = $_.Exception } finally { if ($null -eq $ex) { Log "Copied disk to Azure managed disk '$cloudDiskName'" } else { Log "Deleting managed disk '$cloudDiskName' because upload failed" $null = Remove-AzDisk -ResourceGroupName $targetResourceGroup -DiskName $cloudDiskName -Force throw $ex } } } Function UploadToAzure([string]$destination, [string]$path, [int]$threads, [string]$targetResourceGroup, [string]$cloudDiskName) { $uploadScript = { Copy-ToAzDisk -File $path -Sas $destination -Threads $threads } DoAzureUpload $uploadScript $path $threads $targetResourceGroup $cloudDiskName } Function UploadFromSmbToAzure([string]$destination, [psobject]$smbConfig, [int]$threads, [string]$targetResourceGroup, [string]$cloudDiskName) { $uploadScript = { CopyWithCloudUploader $destination $smbConfig $threads } DoAzureUpload $uploadScript $smbConfig.FileOnShare $threads $targetResourceGroup $cloudDiskName } Function CopyWithCloudUploader([string]$destination, [psobject]$smbConfig, [int]$threads) { $cloudupload = { param($SharePath, $Arguments) $fullPath = Join-Path -Path $SharePath -ChildPath $Arguments[0] Copy-ToAzDisk -File $fullPath -Sas $destination -Threads $threads } ExecuteOnSmbShare $cloudupload $smbConfig.SmbCred $smbConfig.ShareUnc @($smbConfig.ExportFilePath) } Function CleanUpGcpDisk([string]$gcpServiceAccountKeyFile, [string]$cloudDiskName) { $gcpServiceAccountKey = Get-Content -Raw -Path $gcpServiceAccountKeyFile | ConvertFrom-Json try { $disk = Get-GceImage -Name $cloudDiskName -Project $gcpServiceAccountKey.project_id } catch { if ($_.exception.Message -like '*The resource *was not found*') { return } ThrowError ([UploaderException]::new("Error finding disk image $($cloudDiskName)", $_.Exception)) } Log "Deleting existing disk image $cloudDiskName" Remove-GceImage -Name $cloudDiskName -Project $gcpServiceAccountKey.project_id } Function GetAwsCopyArgs([string]$file, [string]$awsProfileName, [string]$awsRegion, [string]$description, [HashTable]$tags, [int]$threads) { $copyArgs = @{File = $file; Description = $description; Tags = $tags; Threads = $threads} if (-not [String]::IsNullOrWhiteSpace($awsProfileName)) { $copyArgs["ProfileName"] = $awsProfileName } if (-not [String]::IsNullOrWhiteSpace($awsRegion)) { $copyArgs["Region"] = $awsRegion } return $copyArgs } Function UploadToAws([string]$path, [string]$awsProfileName, [string]$awsRegion, [string]$description, [HashTable]$tags, [int]$threads) { Log ("Copying disk '$path' to AWS " + $(if ($threads -le 0) {"(threads=default)"} else {"(threads=$threads)"})) $copyArgs = GetAwsCopyArgs $path $awsProfileName $awsRegion $description $tags $threads $snapshotId = Copy-ToAwsDisk @copyArgs Log "Copied disk to AWS snapshot $snapshotId" return $snapshotId } Function UploadFromSmbToAws([psobject]$smbConfig, [string]$awsProfileName, [string]$awsRegion, [string]$description, [HashTable]$tags, [int]$threads) { Log ("Copying disk '$($smbConfig.FileOnShare)' to AWS " + $(if ($threads -le 0) {"(threads=default)"} else {"(threads=$threads)"})) $cloudupload = { param($SharePath, $Arguments) $fullPath = Join-Path -Path $SharePath -ChildPath $Arguments[0] $copyArgs = GetAwsCopyArgs $fullPath $awsProfileName $awsRegion $description $tags $threads Copy-ToAwsDisk @copyArgs } $snapshotId = ExecuteOnSmbShare $cloudupload $smbConfig.SmbCred $smbConfig.ShareUnc @($smbConfig.ExportFilePath) Log "Copied disk to AWS snapshot $snapshotId" return $snapshotId } Function DeriveBucketName([string]$cloudDiskName) { $bucketName = $cloudDiskName.Split('.')[0] if (-not (($bucketName.Length -le 63) -and ($bucketName -cmatch '^[a-z]([-a-z0-9]*[a-z0-9])?$'))) { $msg = "Invalid CloudDiskName '$cloudDiskName'. The stem must meet the requirements for Google Cloud image names. " + "See https://cloud.google.com/compute/docs/reference/rest/v1/images" ThrowError ([UploaderException]::new($msg)) } return $bucketName } Function UploadToGcp([string]$cloudDiskName, [string]$path, [string]$gcpServiceAccountKeyFile) { $bucketName = DeriveBucketName $cloudDiskName Log "Copying disk '$path' to bucket '$bucketName'" Copy-ToGcpDisk -File $path -BucketName $bucketName -ServiceAccountKeyFile $gcpServiceAccountKeyFile Log "Copied disk to image '$cloudDiskName' via bucket '$bucketName'" } Function UploadFromSmbToGcp([string]$cloudDiskName, [string]$gcpServiceAccountKeyFile, [psobject]$smbConfig) { $bucketName = DeriveBucketName $cloudDiskName Log "Copying disk '$($smbConfig.FileOnShare)' to bucket '$bucketName'" $cloudupload = { param($SharePath, $Arguments) $fullPath = Join-Path -Path $SharePath -ChildPath $Arguments[0] Copy-ToGcpDisk -File $fullPath -BucketName $bucketName -ServiceAccountKeyFile $gcpServiceAccountKeyFile } ExecuteOnSmbShare $cloudupload $smbConfig.SmbCred $smbConfig.ShareUnc @($smbConfig.ExportFilePath) Log "Copied disk to image '$cloudDiskName' via bucket '$bucketName'" } Function ExecuteOnSmbShare([ScriptBlock]$scriptblock, [PSCredential]$smbCred, [string]$share, [string[]]$arguments) { $name = "CtxMapping" Log "ExecuteOnSmbShareWithCreds on share $share with args $arguments" $False $err = "" $drive = New-PSDrive -Name $name -PSProvider "FileSystem" -Root $share -Credential $smbCred -Scope Script -ErrorAction SilentlyContinue ` -ErrorVariable err if ($null -eq $drive) { ThrowError ([UploaderException]::new("Failed to map share $share", $_.Exception)) } try { Log "Operating on $($drive.Name) with $arguments" $False $output = & $scriptblock -SharePath "$($name):" -Arguments $arguments Log "Upload successful" $False } finally { try { $null = Remove-PSDrive -Name $name -Force } catch { Log "Failure removing PS drive $($name). $_" $False } } return $output } Function InitUploadLog([string]$logFile, [bool]$overwrite) { if ([String]::IsNullOrWhiteSpace($logFile)) { $logFile = '.\Upload.log' } $Global:UploadLogFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($logFile) $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.ffffZ") if ($overwrite) { "$($timestamp): New log" | Out-File -FilePath $Global:UploadLogFile } else { "$($timestamp): New log" | Out-File -FilePath $Global:UploadLogFile -Append } Write-Host "Logging to $($Global:UploadLogFile)" } Function Log([string]$message, [bool]$echoToScreen = $True) { if ($echoToScreen) { Write-Host $message } $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.ffffZ") "$($timestamp): $message" | Out-File -FilePath $Global:UploadLogFile -Append } Function LogIfSslError([System.Management.Automation.ErrorRecord]$err) { if (CheckForSSLError $err) { Log ("There was a SSL/TLS error while trying to copy the disk. This is commonly caused by there being an intercepting proxy between " + "the machine the command is being run on and the cloud. Check with your network administrator.") } } Function CheckForSSLError([System.Management.Automation.ErrorRecord]$err) { $ex = $err.Exception while ($null -ne $ex) { if ($ex.Message -like '*remote certificate is invalid*' -or $ex.Message -like '*certificate verify failed*') { return $True } $ex = $ex.InnerException } return $False } Function ThrowError([UploaderException]$exception, [bool]$echoToScreen = $True) { if ($null -eq $exception.InnerException) { Log "$($exception.Message)." $echoToScreen } else { Log "$($exception.Message). $($exception.InnerException.Message)" $echoToScreen } throw $exception } Class UploaderException : System.Exception { UploaderException([string]$message) : base($message) { } UploaderException([string]$message, [object]$object) : base($message, $object) { } } |