Private/ResultsAndFiles.ps1
|
function Resolve-DattoApiResultCollection { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object]$Body, [Parameter()] [AllowNull()] [string]$ResultProperty ) if ($null -eq $Body) { return [pscustomobject]@{ PropertyName = $ResultProperty; Values = [object[]]@(); IsEnvelope = $false } } if (-not [string]::IsNullOrWhiteSpace($ResultProperty)) { $property = $Body.PSObject.Properties[$ResultProperty] if ($null -eq $property) { throw [System.IO.InvalidDataException]::new("The Datto response did not contain the expected '$ResultProperty' property.") } return [pscustomobject]@{ PropertyName = $ResultProperty; Values = [object[]]@($property.Value); IsEnvelope = $true } } foreach ($candidate in @('items', 'clients')) { $property = $Body.PSObject.Properties[$candidate] if ($null -ne $property) { return [pscustomobject]@{ PropertyName = $candidate; Values = [object[]]@($property.Value); IsEnvelope = $true } } } if (($Body -is [System.Collections.IEnumerable]) -and -not ($Body -is [string]) -and -not ($Body -is [System.Collections.IDictionary]) -and -not ($Body -is [pscustomobject])) { return [pscustomobject]@{ PropertyName = $null; Values = [object[]]@($Body); IsEnvelope = $false } } return [pscustomobject]@{ PropertyName = $null; Values = [object[]]@($Body); IsEnvelope = $false } } function Get-DattoApiPaginationObject { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object]$Body ) if ($null -ne $Body -and $Body.PSObject.Properties.Name -contains 'pagination') { return $Body.pagination } return $null } function New-DattoApiAggregatedEnvelope { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$FirstBody, [Parameter(Mandatory)] [string]$ResultProperty, [Parameter(Mandatory)] [object[]]$Values, [Parameter(Mandatory)] [int]$FirstPage, [Parameter(Mandatory)] [int]$LastPage, [Parameter(Mandatory)] [int]$PagesRequested ) $output = [ordered]@{} foreach ($property in $FirstBody.PSObject.Properties) { $output[$property.Name] = $property.Value } $output[$ResultProperty] = $Values $output['_dattoAggregation'] = [pscustomobject][ordered]@{ Aggregated = $true FirstPage = $FirstPage LastPage = $LastPage PagesRequested = $PagesRequested ItemCount = $Values.Count } return [pscustomobject]$output } function Get-DattoApiSpecificationDocument { [CmdletBinding()] param() $path = Join-Path -Path $script:DattoApiModuleRoot -ChildPath 'spec/DattoAPI.openapi.json' if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw [System.IO.FileNotFoundException]::new('The bundled Datto OpenAPI specification could not be found.', $path) } return (Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop) } function Resolve-DattoApiOutputPath { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [string]$DefaultFileName, [Parameter(Mandatory)] [string]$RequiredExtension, [Parameter()] [switch]$CreateDirectory ) $candidate = $Path $endsWithSeparator = ( $candidate.EndsWith([string][IO.Path]::DirectorySeparatorChar) -or $candidate.EndsWith([string][IO.Path]::AltDirectorySeparatorChar) -or $candidate.EndsWith('/') -or $candidate.EndsWith('\') ) if ((Test-Path -LiteralPath $candidate -PathType Container) -or $endsWithSeparator) { if (-not (Test-Path -LiteralPath $candidate -PathType Container) -and -not $CreateDirectory) { throw [System.IO.DirectoryNotFoundException]::new("The destination directory does not exist: $candidate") } $candidate = Join-Path -Path $candidate -ChildPath $DefaultFileName } elseif ([string]::IsNullOrWhiteSpace([IO.Path]::GetExtension($candidate))) { $candidate = $candidate + $RequiredExtension } $fullPath = [IO.Path]::GetFullPath($candidate) $parent = Split-Path -Path $fullPath -Parent if (-not (Test-Path -LiteralPath $parent -PathType Container) -and -not $CreateDirectory) { throw [System.IO.DirectoryNotFoundException]::new("The destination directory does not exist: $parent") } return $fullPath } function ConvertTo-DattoApiSafeFileComponent { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Value ) $invalid = @([IO.Path]::GetInvalidFileNameChars()) + @('<', '>', ':', '"', '/', '\', '|', '?', '*') $builder = [Text.StringBuilder]::new() foreach ($character in $Value.ToCharArray()) { if ($invalid -contains $character) { $null = $builder.Append('_') } else { $null = $builder.Append($character) } } return $builder.ToString() } function Test-DattoApiByteSequence { [CmdletBinding()] param( [Parameter(Mandatory)] [byte[]]$Bytes, [Parameter(Mandatory)] [byte[]]$Sequence, [Parameter()] [switch]$AtEnd ) if ($Bytes.Length -lt $Sequence.Length) { return $false } $offset = 0 if ($AtEnd) { $offset = $Bytes.Length - $Sequence.Length } for ($index = 0; $index -lt $Sequence.Length; $index++) { if ($Bytes[$offset + $index] -ne $Sequence[$index]) { return $false } } return $true } function Assert-DattoApiImageResponse { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Response, [Parameter(Mandatory)] [ValidateSet('Png', 'Jpeg')] [string]$ImageType ) if ($null -eq $Response.Bytes -or $Response.Bytes.Length -eq 0) { throw [System.IO.InvalidDataException]::new('Datto returned an empty screenshot response.') } switch ($ImageType) { 'Png' { $expectedContentType = 'image/png' $validStart = Test-DattoApiByteSequence -Bytes $Response.Bytes -Sequence ([byte[]](137, 80, 78, 71, 13, 10, 26, 10)) $validEnd = Test-DattoApiByteSequence -Bytes $Response.Bytes -Sequence ([byte[]](0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130)) -AtEnd } 'Jpeg' { $expectedContentType = 'image/jpeg' $validStart = Test-DattoApiByteSequence -Bytes $Response.Bytes -Sequence ([byte[]](255, 216, 255)) $validEnd = Test-DattoApiByteSequence -Bytes $Response.Bytes -Sequence ([byte[]](255, 217)) -AtEnd } } if (-not [string]::IsNullOrWhiteSpace([string]$Response.ContentType) -and [string]$Response.ContentType -ine $expectedContentType) { throw [System.IO.InvalidDataException]::new("Datto returned content type '$($Response.ContentType)' where '$expectedContentType' was expected.") } if (-not $validStart -or -not $validEnd) { throw [System.IO.InvalidDataException]::new("Datto returned bytes that do not have a valid $ImageType image signature.") } } function Write-DattoApiFileAtomically { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [byte[]]$Bytes, [Parameter()] [switch]$Force ) $directory = Split-Path -Path $Path -Parent $fileName = [IO.Path]::GetFileName($Path) $temporaryPath = Join-Path -Path $directory -ChildPath ('.{0}.{1}.tmp' -f $fileName, [guid]::NewGuid().ToString('N')) try { $stream = [IO.FileStream]::new( $temporaryPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None ) try { $stream.Write($Bytes, 0, $Bytes.Length) $stream.Flush($true) } finally { $stream.Dispose() } if ([IO.File]::Exists($Path)) { if (-not $Force) { throw [System.IO.IOException]::new("The destination file already exists: $Path. Use -Force to overwrite it.") } [IO.File]::Replace($temporaryPath, $Path, $null) } else { try { [IO.File]::Move($temporaryPath, $Path) } catch [System.IO.IOException] { if (-not $Force -or -not [IO.File]::Exists($Path)) { throw } [IO.File]::Replace($temporaryPath, $Path, $null) } } $temporaryPath = $null } finally { if (-not [string]::IsNullOrWhiteSpace($temporaryPath) -and [IO.File]::Exists($temporaryPath)) { [IO.File]::Delete($temporaryPath) } } } |