ConfluencePS.psm1
#region Dependencies # Load the ConfluencePS namespace from C# if (!("ConfluencePS.Space" -as [Type])) { Add-Type -Path (Join-Path $PSScriptRoot ConfluencePS.Types.cs) -ReferencedAssemblies Microsoft.CSharp, Microsoft.PowerShell.Commands.Utility, System.Management.Automation } # Load Web assembly when needed # PowerShell Core has the assembly preloaded if (!("System.Web.HttpUtility" -as [Type])) { Add-Type -Assembly System.Web } #endregion Dependencies function Add-Attachment { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true )] [OutputType([ConfluencePS.Attachment])] param( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [Int]$PageID, [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName )] [ValidateScript( { if (-not (Test-Path $_ -PathType Leaf)) { $errorItem = [System.Management.Automation.ErrorRecord]::new( ([System.ArgumentException]"File not found"), 'ParameterValue.FileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $_ ) $errorItem.ErrorDetails = "No file could be found with the provided path '$_'." $PSCmdlet.ThrowTerminatingError($errorItem) } else { return $true } } )] [Alias('InFile', 'FullName', 'Path', 'PSPath')] [String[]] $FilePath ) begin { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/child/attachment" } process { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $parameter = @{ URI = $resourceApi -f $PageID Method = "POST" Credential = $Credential OutputType = [ConfluencePS.Attachment] Verbose = $false } foreach ($file in $FilePath) { $parameter["InFile"] = $file Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking Add Attachment Method with `$parameter" if ($PSCmdlet.ShouldProcess($PageID, "Adding attachment(s) '$($file)'.")) { Invoke-Method @parameter } } } end { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete" } } function Add-Label { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true )] [OutputType([ConfluencePS.ContentLabelSet])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias('Labels')] $Label ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/label" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" # Validade input object from Pipeline if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int] -or $_ -is [ConfluencePS.ContentLabelSet])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } # The parameter "Label" has no type declared. Because of this, a piped object of # type "ConfluencePS.ContentLabelSet" will be assigned to "Label". Lets fix this: if ($_ -and $Label -is [ConfluencePS.ContentLabelSet]) { $Label = $Label.Labels } # Test if Label is String[] [String[]]$_label = $Label $_label = $_label | Where-Object {$_ -ne "ConfluencePS.Label"} if ($_label) { [String[]]$Label = $_label } # Allow only for Label to be a [String[]] or [ConfluencePS.Label[]] $allowedLabelTypes = @( "System.String" "System.String[]" "ConfluencePS.Label" "ConfluencePS.Label[]" ) if ($Label.GetType().FullName -notin $allowedLabelTypes) { $message = "Parameter 'Label' is not a Label or a String. It is $($Label.gettype().FullName)" $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Post' Body = "" OutputType = [ConfluencePS.Label] Credential = $Credential } # Extract name if an Object is provided if (($Label -is [ConfluencePS.Label]) -or $Label -is [ConfluencePS.Label[]]) { $Label = $Label | Select-Object -ExpandProperty Name } foreach ($_page in $PageID) { if ($_ -is [ConfluencePS.Page]) { $InputObject = $_ } elseif ($_ -is [ConfluencePS.ContentLabelSet]) { $InputObject = $_.Page } else { $InputObject = Get-Page -PageID $_page -ApiURi $apiURi -Credential $Credential } $iwParameters["Uri"] = $resourceApi -f $_page $iwParameters["Body"] = ($Label | Foreach-Object {@{prefix = 'global'; name = $_}}) | ConvertTo-Json Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($iwParameters["Body"] | Out-String)" If ($PSCmdlet.ShouldProcess("Label $Label, PageID $_page")) { $output = [ConfluencePS.ContentLabelSet]@{ Page = $InputObject } $output.Labels += (Invoke-Method @iwParameters) $output } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function ConvertTo-StorageFormat { [CmdletBinding()] [OutputType([String])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [string[]]$Content ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" foreach ($_content in $Content) { $iwParameters = @{ Uri = "$apiURi/contentbody/convert/storage" Method = 'Post' Body = @{ value = "$_content" representation = 'wiki' } | ConvertTo-Json Credential = $Credential } Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($_content | Out-String)" (Invoke-Method @iwParameters).value } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function ConvertTo-Table { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', '')] param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true )] $Content, [switch]$NoHeader ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $RowArray = New-Object System.Collections.ArrayList } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" If ($NoHeader) { $HeaderGenerated = $true } # This ForEach needed if the content wasn't piped in $Content | ForEach-Object { # First row enclosed by ||, all other rows by | If (!$HeaderGenerated) { $_.PSObject.Properties | ForEach-Object -Begin {$Header = ""} ` -Process {$Header += "||$($_.Name)"} ` -End {$Header += "||"} $RowArray.Add($Header) | Out-Null $HeaderGenerated = $true } $_.PSObject.Properties | ForEach-Object -Begin {$Row = ""} ` -Process {$Row += "|$($_.Value)"} ` -End {$Row += "|"} $RowArray.Add($Row) | Out-Null } } END { $RowArray | Out-String Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ened" } } function Get-Attachment { [CmdletBinding( SupportsPaging = $true )] [OutputType([ConfluencePS.Attachment])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [Int[]]$PageID, [String]$FileNameFilter, [String]$MediaTypeFilter, [ValidateRange(1, [int]::MaxValue)] [Int]$PageSize = 25 ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/child/attachment" } PROCESS { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } foreach ($_PageID in $PageID) { $iwParameters = @{ Uri = $resourceApi -f $_PageID Method = 'Get' GetParameters = @{ expand = "version" limit = $PageSize } OutputType = [ConfluencePS.Attachment] Credential = $Credential } if ($FileNameFilter) { $iwParameters["GetParameters"]["filename"] = $FileNameFilter } if ($MediaTypeFilter) { $iwParameters["GetParameters"]["mediaType"] = $MediaTypeFilter } # Paging ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object { $iwParameters[$_] = $PSCmdlet.PagingParameters.$_ } Invoke-Method @iwParameters } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Get-AttachmentFile { [CmdletBinding()] [OutputType([Bool])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ConfluencePS.Attachment[]]$Attachment, [ValidateScript( { if (-not (Test-Path $_)) { $errorItem = [System.Management.Automation.ErrorRecord]::new( ([System.ArgumentException]"Path not found"), 'ParameterValue.FileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $_ ) $errorItem.ErrorDetails = "Invalid path '$_'." $PSCmdlet.ThrowTerminatingError($errorItem) } else { return $true } } )] [String]$Path ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" } PROCESS { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Attachment])) { $message = "The Object in the pipe is not an Attachment." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } foreach ($_Attachment in $Attachment) { if ($Path) { $filename = Join-Path $Path $_Attachment.Filename } else { $filename = $_Attachment.Filename } $iwParameters = @{ Uri = $_Attachment.URL Method = 'Get' Headers = @{"Accept" = $_Attachment.MediaType} OutFile = $filename Credential = $Credential } $result = Invoke-Method @iwParameters (-not $result) } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Get-ChildPage { [CmdletBinding( SupportsPaging = $true )] [OutputType([ConfluencePS.Page])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int]$PageID, [switch]$Recurse, [ValidateRange(1, [int]::MaxValue)] [int]$PageSize = 25 ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $depthLevel = "child" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } if ($PsCmdlet.ParameterSetName -eq "byObject") { $PageID = $InputObject.ID } if ($Recurse) { $depthLevel = "descendant" } # depth = ALL $iwParameters = @{ Uri = "$apiURi/content/{0}/{1}/page" -f $PageID, $depthLevel Method = 'Get' GetParameters = @{ expand = "space,version,body.storage,ancestors" limit = $PageSize } OutputType = [ConfluencePS.Page] Credential = $Credential } # Paging ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object { $iwParameters[$_] = $PSCmdlet.PagingParameters.$_ } Invoke-Method @iwParameters } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Get-Label { [CmdletBinding( SupportsPaging = $true )] [OutputType([ConfluencePS.ContentLabelSet])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID, [ValidateRange(1, [int]::MaxValue)] [int]$PageSize = 25 ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/label" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Get' GetParameters = @{ limit = $PageSize } OutputType = [ConfluencePS.Label] Credential = $Credential } # Paging ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object { $iwParameters[$_] = $PSCmdlet.PagingParameters.$_ } foreach ($_page in $PageID) { if ($_ -is [ConfluencePS.Page]) { $InputObject = $_ } else { $InputObject = Get-Page -PageID $_page -ApiURi $apiURi -Credential $Credential } $iwParameters["Uri"] = $resourceApi -f $_page Write-debug "Hey" $output = New-Object -TypeName ConfluencePS.ContentLabelSet $output.Page = $InputObject $output.Labels += (Invoke-Method @iwParameters) $output } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Get-Page { [CmdletBinding( SupportsPaging = $true, DefaultParameterSetName = "byId" )] [OutputType([ConfluencePS.Page])] param ( [Parameter( Mandatory = $true )] [URi]$ApiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ParameterSetName = "byId", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID, [Parameter( ParameterSetName = "bySpace" )] [Parameter( ParameterSetName = "bySpaceObject" )] [Alias('Name')] [string]$Title, [Parameter( Mandatory = $true, ParameterSetName = "bySpace" )] [Parameter( ParameterSetName = "byLabel" )] [Alias('Key')] [string]$SpaceKey, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "bySpaceObject" )] [Parameter( ValueFromPipeline = $true, ParameterSetName = "byLabel" )] [ConfluencePS.Space]$Space, [Parameter( Mandatory = $true, ParameterSetName = "byLabel" )] [string[]]$Label, [Parameter( Position = 0, Mandatory = $true, ParameterSetName = "byQuery" )] [string]$Query, [ValidateRange(1, [int]::MaxValue)] [int]$PageSize = 25 ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content{0}" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if ($Space -is [ConfluencePS.Space] -and ($Space.Key)) { $SpaceKey = $Space.Key } $iwParameters = @{ Uri = "" Method = 'Get' GetParameters = @{ expand = "space,version,body.storage,ancestors" limit = $PageSize } OutputType = [ConfluencePS.Page] Credential = $Credential } # Paging ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object { $iwParameters[$_] = $PSCmdlet.PagingParameters.$_ } switch -regex ($PsCmdlet.ParameterSetName) { "byId" { foreach ($_pageID in $PageID) { $iwParameters["Uri"] = $resourceApi -f "/$_pageID" Invoke-Method @iwParameters } break } "bySpace" { # This includes 'bySpaceObject' $iwParameters["Uri"] = $resourceApi -f '' $iwParameters["GetParameters"]["type"] = "page" if ($SpaceKey) { $iwParameters["GetParameters"]["spaceKey"] = $SpaceKey } if ($Title) { Invoke-Method @iwParameters | Where-Object {$_.Title -like "$Title"} } else { Invoke-Method @iwParameters } break } "byLabel" { $iwParameters["Uri"] = $resourceApi -f "/search" $CQLparameters = @("type=page", "label=$Label") if ($SpaceKey) {$CQLparameters += "space=$SpaceKey"} $cqlQuery = ConvertTo-URLEncoded ($CQLparameters -join (" AND ")) $iwParameters["GetParameters"]["cql"] = $cqlQuery Invoke-Method @iwParameters break } "byQuery" { $iwParameters["Uri"] = $resourceApi -f "/search" $cqlQuery = ConvertTo-URLEncoded $Query $iwParameters["GetParameters"]["cql"] = "type=page AND $cqlQuery" Invoke-Method @iwParameters } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Get-Space { [CmdletBinding( SupportsPaging = $true )] [OutputType([ConfluencePS.Space])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0 )] [Alias('Key')] [string[]]$SpaceKey, [ValidateRange(1, [int]::MaxValue)] [int]$PageSize = 25 ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/space{0}" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $iwParameters = @{ Uri = "" Method = 'Get' GetParameters = @{ expand = "description.plain,icon,homepage,metadata.labels" limit = $PageSize } OutputType = [ConfluencePS.Space] Credential = $Credential } # Paging ($PSCmdlet.PagingParameters | Get-Member -MemberType Property).Name | ForEach-Object { $iwParameters[$_] = $PSCmdlet.PagingParameters.$_ } if ($SpaceKey) { foreach ($_space in $SpaceKey) { $iwParameters["Uri"] = $resourceApi -f "/$_space" Invoke-Method @iwParameters } } else { $iwParameters["Uri"] = $resourceApi -f "" Invoke-Method @iwParameters } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Invoke-Method { [CmdletBinding(SupportsPaging = $true)] [OutputType( [PSObject], [ConfluencePS.Page], [ConfluencePS.Space], [ConfluencePS.Label], [ConfluencePS.Icon], [ConfluencePS.Version], [ConfluencePS.User], [ConfluencePS.Attachment] )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( "PSAvoidUsingEmptyCatchBlock", "" )] param ( [Parameter(Mandatory = $true)] [Uri]$URi, [Microsoft.PowerShell.Commands.WebRequestMethod]$Method = "GET", [ValidateNotNullOrEmpty()] [String]$Body, [Switch]$RawBody, [Hashtable]$Headers, [Hashtable]$GetParameters, [String]$InFile, [String]$OutFile, [ValidateSet( [ConfluencePS.Page], [ConfluencePS.Space], [ConfluencePS.Label], [ConfluencePS.Icon], [ConfluencePS.Version], [ConfluencePS.User], [ConfluencePS.Attachment] )] [System.Type]$OutputType, [Parameter(Mandatory = $true)] [PSCredential]$Credential, $Caller = $PSCmdlet ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" # pass input to local variable # this allows to use the PSBoundParameters for recursion $_headers = @{ # Set any default headers "Accept" = "application/json" "Accept-Charset" = "utf-8" } $Headers.Keys.foreach( { $_headers[$_] = $Headers[$_] }) } Process { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" # load DefaultParameters for Invoke-WebRequest # as the global PSDefaultParameterValues is not used $PSDefaultParameterValues = $global:PSDefaultParameterValues # Append GET parameters to URi if (($PSCmdlet.PagingParameters) -and ($PSCmdlet.PagingParameters.Skip)) { $GetParameters["start"] = $PSCmdlet.PagingParameters.Skip } if ($GetParameters -and ($URi -notlike "*\?*")) { Write-Debug "[$($MyInvocation.MyCommand.Name)] Using `$GetParameters: $($GetParameters | Out-String)" [Uri]$URI = "$Uri$(ConvertTo-GetParameter $GetParameters)" # Prevent recursive appends $GetParameters = $null } # set mandatory parameters $splatParameters = @{ Uri = $URi Method = $Method Headers = $_headers ContentType = "application/json; charset=utf-8" UseBasicParsing = $true Credential = $Credential ErrorAction = "Stop" Verbose = $false # Overwrites verbose output } if ($_headers.ContainsKey("Content-Type")) { $splatParameters["ContentType"] = $_headers["Content-Type"] $_headers.Remove("Content-Type") $splatParameters["Headers"] = $_headers } if ($Body) { if ($RawBody) { $splatParameters["Body"] = $Body } else { # Encode Body to preserve special chars # http://stackoverflow.com/questions/15290185/invoke-webrequest-issue-with-special-characters-in-json $splatParameters["Body"] = [System.Text.Encoding]::UTF8.GetBytes($Body) } } if ($InFile) { $splatParameters["InFile"] = $InFile } if ($OutFile) { $splatParameters["OutFile"] = $OutFile } # Invoke the API Write-Verbose "[$($MyInvocation.MyCommand.Name)] Invoking method $Method to URI $URi" Write-Verbose "[$($MyInvocation.MyCommand.Name)] Invoke-WebRequest with: $(([PSCustomObject]$splatParameters) | Out-String)" try { $webResponse = Invoke-WebRequest @splatParameters } catch { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Failed to get an answer from the server" $webResponse = $_ if ($webResponse.ErrorDetails) { # In PowerShellCore (v6+), the response body is available as string $responseBody = $webResponse.ErrorDetails.Message } else { $webResponse = $webResponse.Exception.Response } } # Test response Headers if Confluence requires a CAPTCHA Test-Captcha -InputObject $webResponse Write-Debug "[$($MyInvocation.MyCommand.Name)] Executed WebRequest. Access `$webResponse to see details" if ($webResponse) { # In PowerShellCore (v6+) the StatusCode of an exception is somewhere else if (-not ($statusCode = $webResponse.StatusCode)) { $statusCode = $webresponse.Exception.Response.StatusCode } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Status code: $($statusCode)" if ($statusCode.value__ -ge 400) { Write-Warning "Confluence returned HTTP error $($statusCode.value__) - $($statusCode)" if ((!($responseBody)) -and ($webResponse | Get-Member -Name "GetResponseStream")) { # Retrieve body of HTTP response - this contains more useful information about exactly why the error occurred $readStream = New-Object -TypeName System.IO.StreamReader -ArgumentList ($webResponse.GetResponseStream()) $responseBody = $readStream.ReadToEnd() $readStream.Close() } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Retrieved body of HTTP response for more information about the error (`$responseBody)" Write-Debug "[$($MyInvocation.MyCommand.Name)] Got the following error as `$responseBody" $errorItem = [System.Management.Automation.ErrorRecord]::new( ([System.ArgumentException]"Invalid Server Response"), "InvalidResponse.Status$($statusCode.value__)", [System.Management.Automation.ErrorCategory]::InvalidResult, $responseBody ) try { $responseObject = ConvertFrom-Json -InputObject $responseBody -ErrorAction Stop if ($responseObject.message) { $errorItem.ErrorDetails = $responseObject.message } else { $errorItem.ErrorDetails = "An unknown error ocurred." } } catch { $errorItem.ErrorDetails = "An unknown error ocurred." } $Caller.WriteError($errorItem) } else { if ($webResponse.Content) { try { # API returned a Content: lets work with it $response = ConvertFrom-Json ([Text.Encoding]::UTF8.GetString($webResponse.RawContentStream.ToArray())) if ($null -ne $response.errors) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] An error response was received from; resolving" # This could be handled nicely in an function such as: # ResolveError $response -WriteError Write-Error $($response.errors | Out-String) } else { if ($PSCmdlet.PagingParameters.IncludeTotalCount) { [double]$Accuracy = 0.0 $PSCmdlet.PagingParameters.NewTotalCount($response.size, $Accuracy) } # None paginated results / first page of pagination $result = $response if (($response) -and ($response | Get-Member -Name results)) { $result = $response.results } if ($OutputType) { # Results shall be casted to custom objects (see ValidateSet) Write-Verbose "[$($MyInvocation.MyCommand.Name)] Outputting results as $($OutputType.FullName)" $converter = "ConvertTo-$($OutputType.Name)" $result | & $converter } else { $result } # Detect if result is paginated if ($response._links.next) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Invoking pagination" # Remove Parameters that don't need propagation $script:PSDefaultParameterValues.Remove("$($MyInvocation.MyCommand.Name):GetParameters") $script:PSDefaultParameterValues.Remove("$($MyInvocation.MyCommand.Name):IncludeTotalCount") # Self-Invoke function for recursion $parameters = @{ URi = "{0}{1}" -f $response._links.base, $response._links.next Method = $Method Credential = $Credential } if ($Body) {$parameters["Body"] = $Body} if ($Headers) {$parameters["Headers"] = $Headers} if ($OutputType) {$parameters["OutputType"] = $OutputType} Write-Verbose "NEXT PAGE: $($parameters["URi"])" Invoke-Method @parameters } } } catch { throw $_ } } else { # No content, although statusCode < 400 # This could be wanted behavior of the API Write-Verbose "[$($MyInvocation.MyCommand.Name)] No content was returned from." } } } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] No Web result object was returned from. This is unusual!" } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function New-Page { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true, DefaultParameterSetName = 'byParameters' )] [OutputType([ConfluencePS.Page])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'byObject' )] [ConfluencePS.Page]$InputObject, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'byParameters' )] [Alias('Name')] [string]$Title, [Parameter(ParameterSetName = 'byParameters')] [ValidateRange(1, [int]::MaxValue)] [int]$ParentID, [Parameter(ParameterSetName = 'byParameters')] [ConfluencePS.Page]$Parent, [Parameter(ParameterSetName = 'byParameters')] [string]$SpaceKey, [Parameter(ParameterSetName = 'byParameters')] [ConfluencePS.Space]$Space, [Parameter(ParameterSetName = 'byParameters')] [string]$Body, [Parameter(ParameterSetName = 'byParameters')] [switch]$Convert ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $iwParameters = @{ Uri = $resourceApi Method = 'Post' Body = "" OutputType = [ConfluencePS.Page] Credential = $Credential } $Content = [PSObject]@{ type = "page" space = [PSObject]@{ key = ""} title = "" body = [PSObject]@{ storage = [PSObject]@{ representation = 'storage' } } ancestors = @() } switch ($PsCmdlet.ParameterSetName) { "byObject" { $Content.title = $InputObject.Title $Content.space.key = $InputObject.Space.Key $Content.body.storage.value = $InputObject.Body if ($InputObject.Ancestors) { $Content.ancestors += @( $InputObject.Ancestors | Foreach-Object { @{ id = $_.ID } } ) } } "byParameters" { if (($Parent -is [ConfluencePS.Page]) -and ($Parent.ID)) { $ParentID = $Parent.ID } if (($Space -is [ConfluencePS.Space]) -and ($Space.Key)) { $SpaceKey = $Space.Key } If (($ParentID) -and !($SpaceKey)) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] SpaceKey not specified. Retrieving from Get-ConfluencePage -PageID $ParentID" $SpaceKey = (Get-Page -PageID $ParentID -ApiURi $apiURi -Credential $Credential).Space.Key } # If -Convert is flagged, call ConvertTo-ConfluenceStorageFormat against the -Body If ($Convert) { Write-Verbose '[$($MyInvocation.MyCommand.Name)] -Convert flag active; converting content to Confluence storage format' $Body = ConvertTo-StorageFormat -Content $Body -ApiURi $apiURi -Credential $Credential } $Content.title = $Title $Content.space = @{ key = $SpaceKey } $Content.body.storage.value = $Body if ($ParentID) { $Content.ancestors = @( @{ id = $ParentID } ) } } } $iwParameters["Body"] = $Content | ConvertTo-Json Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($Content | Out-String)" If ($PSCmdlet.ShouldProcess("Space $($Content.space.key)")) { Invoke-Method @iwParameters } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function New-Space { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true, DefaultParameterSetName = "byObject" )] [OutputType([ConfluencePS.Space])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = "byObject", ValueFromPipeline = $true )] [ConfluencePS.Space]$InputObject, [Parameter( Mandatory = $true, ParameterSetName = "byProperties" )] [Alias('Key')] [string]$SpaceKey, [Parameter( Mandatory = $true, ParameterSetName = "byProperties" )] [string]$Name, [Parameter( ParameterSetName = "byProperties" )] [string]$Description ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/space" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if ($PsCmdlet.ParameterSetName -eq "byObject") { $SpaceKey = $InputObject.Key $Name = $InputObject.Name $Description = $InputObject.Description } $iwParameters = @{ Uri = $resourceApi Method = 'Post' Body = "" OutputType = [ConfluencePS.Space] Credential = $Credential } $Body = @{ key = $SpaceKey name = $Name description = @{ plain = @{ value = $Description representation = 'plain' } } } $iwParameters["Body"] = $Body | ConvertTo-Json Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($Body | Out-String)" If ($PSCmdlet.ShouldProcess("$SpaceKey $Name")) { Invoke-Method @iwParameters } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Remove-Attachment { [CmdletBinding( ConfirmImpact = 'Medium', SupportsShouldProcess = $true )] [OutputType([Bool])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ConfluencePS.Attachment[]]$Attachment ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}" } PROCESS { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $iwParameters = @{ Uri = "" Method = 'Delete' Credential = $Credential } foreach ($_attachment in $Attachment) { $iwParameters["Uri"] = $resourceApi -f $_attachment.ID If ($PSCmdlet.ShouldProcess("Attachment $($_attachment.ID), PageID $($_attachment.PageID)")) { Invoke-Method @iwParameters } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Remove-Label { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true )] [OutputType([Bool])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID, [Parameter()] [string[]]$Label ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/label?name={1}" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Delete' Credential = $Credential } foreach ($_page in $PageID) { $_labels = $Label if (!$_labels) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Collecting all Labels for page $_page" $allLabels = Get-Label -PageID $_page -ApiURi $apiURi -Credential $Credential if ($allLabels.Labels) { $_labels = $allLabels.Labels | Select-Object -ExpandProperty Name } } Write-Debug "[$($MyInvocation.MyCommand.Name)] Labels to remove: `$_labels" foreach ($_label in $_labels) { $iwParameters["Uri"] = $resourceApi -f $_page, $_label If ($PSCmdlet.ShouldProcess("Label $_label, PageID $_page")) { Invoke-Method @iwParameters } } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Remove-Page { [CmdletBinding( ConfirmImpact = 'Medium', SupportsShouldProcess = $true )] [OutputType([Bool])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Delete' Credential = $Credential } foreach ($_page in $PageID) { $iwParameters["Uri"] = $resourceApi -f $_page If ($PSCmdlet.ShouldProcess("PageID $_page")) { Invoke-Method @iwParameters } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Remove-Space { [CmdletBinding( ConfirmImpact = 'High', SupportsShouldProcess = $true )] [OutputType()] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssignments', '')] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias('Key')] [string[]]$SpaceKey, [switch]$Force # TODO: Probably an extra param later to loop checking the status & wait for completion? ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/space/{0}" if ($Force) { Write-Debug "[$($MyInvocation.MyCommand.Name)] -Force was passed. Backing up current ConfirmPreference [$ConfirmPreference] and setting to None" $oldConfirmPreference = $ConfirmPreference $ConfirmPreference = 'None' } } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Space] -or $_ -is [string])) { $message = "The Object in the pipe is not a Space." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Delete' Credential = $Credential } foreach ($_space in $SpaceKey) { $iwParameters["Uri"] = $resourceApi -f $_space If ($PSCmdlet.ShouldProcess("Space key $_space")) { $response = Invoke-Method @iwParameters # Successful response provides a "longtask" status link # (add additional code here later to check and/or wait for the status) } } } END { if ($Force) { Write-Debug "[$($MyInvocation.MyCommand.Name)] Restoring ConfirmPreference to [$oldConfirmPreference]" $ConfirmPreference = $oldConfirmPreference } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Set-Attachment { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true )] [OutputType([ConfluencePS.Attachment])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ConfluencePS.Attachment]$Attachment, # Path of the file to upload and attach [Parameter( Mandatory )] [ValidateScript( { if (-not (Test-Path $_ -PathType Leaf)) { $errorItem = [System.Management.Automation.ErrorRecord]::new( ([System.ArgumentException]"File not found"), 'ParameterValue.FileNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $_ ) $errorItem.ErrorDetails = "No file could be found with the provided path '$_'." $PSCmdlet.ThrowTerminatingError($errorItem) } else { return $true } } )] [String]$FilePath ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/child/attachment/{1}/data" } PROCESS { Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-DebugMessage "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $parameter = @{ URI = $resourceApi -f $Attachment.PageID, $Attachment.ID Method = "POST" InFile = $FilePath Credential = $Credential OutputType = [ConfluencePS.Attachment] Verbose = $false } Write-Debug "[$($MyInvocation.MyCommand.Name)] Invoking Set Attachment Method with `$parameter" if ($PSCmdlet.ShouldProcess($Attachment.PageID, "Updating attachment '$($Attachment.Title)'.")) { Invoke-Method @parameter } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Set-Info { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] param ( [Parameter( HelpMessage = 'Example = https://brianbunke.atlassian.net/wiki (/wiki for Cloud instances)' )] [Uri]$BaseURi, [PSCredential]$Credential, [int]$PageSize, [switch]$PromptCredentials ) BEGIN { function Add-ConfluenceDefaultParameter { param( [Parameter(Mandatory = $true)] [string]$Command, [Parameter(Mandatory = $true)] [string]$Parameter, [Parameter(Mandatory = $true)] $Value ) PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Setting [$command : $parameter] = $value" # Needs to set both global and module scope for the private functions: # http://stackoverflow.com/questions/30427110/set-psdefaultparametersvalues-for-use-within-module-scope $PSDefaultParameterValues["${command}:${parameter}"] = $Value $global:PSDefaultParameterValues["${command}:${parameter}"] = $Value } } $moduleCommands = Get-Command -Module ConfluencePS if ($PromptCredentials) { $Credential = (Get-Credential) } } PROCESS { foreach ($command in $moduleCommands) { $parameter = "ApiURi" if ($BaseURi -and ($command.Parameters.Keys -contains $parameter)) { Add-ConfluenceDefaultParameter -Command $command -Parameter $parameter -Value ($BaseURi.AbsoluteUri.TrimEnd('/') + '/rest/api') } $parameter = "Credential" if ($Credential -and ($command.Parameters.Keys -contains $parameter)) { Add-ConfluenceDefaultParameter -Command $command -Parameter $parameter -Value $Credential } $parameter = "PageSize" if ($PageSize -and ($command.Parameters.Keys -contains $parameter)) { Add-ConfluenceDefaultParameter -Command $command -Parameter $parameter -Value $PageSize } } } } function Set-Label { [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $true )] [OutputType([ConfluencePS.ContentLabelSet])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int[]]$PageID, [Parameter(Mandatory = $true)] [string[]]$Label ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}/label" } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" if (($_) -and -not($_ -is [ConfluencePS.Page] -or $_ -is [int])) { $message = "The Object in the pipe is not a Page." $exception = New-Object -TypeName System.ArgumentException -ArgumentList $message Throw $exception } $iwParameters = @{ Uri = "" Method = 'Post' Body = "" OutputType = [ConfluencePS.Label] Credential = $Credential } foreach ($_page in $PageID) { if ($_ -is [ConfluencePS.Page]) { $InputObject = $_ } else { $InputObject = Get-Page -PageID $_page -ApiURi $apiURi -Credential $Credential } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Removing all previous labels" Remove-Label -PageID $_page -ApiURi $apiURi -Credential $Credential | Out-Null $iwParameters["Uri"] = $resourceApi -f $_page $iwParameters["Body"] = $Label | Foreach-Object {@{prefix = 'global'; name = $_}} | ConvertTo-Json Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($iwParameters["Body"] | Out-String)" If ($PSCmdlet.ShouldProcess("Label $Label, PageID $_page")) { $output = [ConfluencePS.ContentLabelSet]@{ Page = $InputObject } $output.Labels += (Invoke-Method @iwParameters) $output } } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function Set-Page { [CmdletBinding( ConfirmImpact = 'Medium', SupportsShouldProcess = $true, DefaultParameterSetName = 'byParameters' )] [OutputType([ConfluencePS.Page])] param ( [Parameter( Mandatory = $true )] [URi]$apiURi, [Parameter( Mandatory = $true )] [PSCredential]$Credential, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'byObject' )] [ConfluencePS.Page]$InputObject, [Parameter( Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'byParameters' )] [ValidateRange(1, [int]::MaxValue)] [Alias('ID')] [int]$PageID, [Parameter(ParameterSetName = 'byParameters')] [ValidateNotNullOrEmpty()] [string]$Title, [Parameter(ParameterSetName = 'byParameters')] [string]$Body, [Parameter(ParameterSetName = 'byParameters')] [switch]$Convert, [Parameter(ParameterSetName = 'byParameters')] [ValidateRange(1, [int]::MaxValue)] [int]$ParentID, [Parameter(ParameterSetName = 'byParameters')] [ConfluencePS.Page]$Parent ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $resourceApi = "$apiURi/content/{0}" # If -Convert is flagged, call ConvertTo-ConfluenceStorageFormat against the -Body If ($Convert) { Write-Verbose '[$($MyInvocation.MyCommand.Name)] -Convert flag active; converting content to Confluence storage format' $Body = ConvertTo-StorageFormat -Content $Body -ApiURi $apiURi -Credential $Credential } } PROCESS { Write-Debug "[$($MyInvocation.MyCommand.Name)] ParameterSetName: $($PsCmdlet.ParameterSetName)" Write-Debug "[$($MyInvocation.MyCommand.Name)] PSBoundParameters: $($PSBoundParameters | Out-String)" $iwParameters = @{ Uri = "" Method = 'Put' Body = "" OutputType = [ConfluencePS.Page] Credential = $Credential } $Content = [PSObject]@{ type = "page" title = "" body = [PSObject]@{ storage = [PSObject]@{ value = "" representation = 'storage' } } version = [PSObject]@{ number = 0 } ancestors = @() } switch ($PsCmdlet.ParameterSetName) { "byObject" { $iwParameters["Uri"] = $resourceApi -f $InputObject.ID $Content.version.number = ++$InputObject.Version.Number $Content.title = $InputObject.Title $Content.body.storage.value = $InputObject.Body # if ($InputObject.Ancestors) { # $Content["ancestors"] += @( $InputObject.Ancestors | Foreach-Object { @{ id = $_.ID } } ) # } } "byParameters" { $iwParameters["Uri"] = $resourceApi -f $PageID $originalPage = Get-Page -PageID $PageID -ApiURi $apiURi -Credential $Credential if (($Parent -is [ConfluencePS.Page]) -and ($Parent.ID)) { $ParentID = $Parent.ID } $Content.version.number = ++$originalPage.Version.Number if ($Title) { $Content.title = $Title } else { $Content.title = $originalPage.Title } # $Body might be empty if ($PSBoundParameters.Keys -contains "Body") { $Content.body.storage.value = $Body } else { $Content.body.storage.value = $originalPage.Body } # Ancestors is undocumented! May break in the future # http://stackoverflow.com/questions/23523705/how-to-create-new-page-in-confluence-using-their-rest-api if ($ParentID) { $Content.ancestors = @( @{ id = $ParentID } ) } } } $iwParameters["Body"] = $Content | ConvertTo-Json Write-Debug "[$($MyInvocation.MyCommand.Name)] Content to be sent: $($Content | Out-String)" If ($PSCmdlet.ShouldProcess("Page $($Content.title)")) { Invoke-Method @iwParameters } } END { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function ended" } } function ConvertFrom-HTMLEncoded { <# .SYNOPSIS Decode a HTML encoded string #> [CmdletBinding()] [OutputType([String])] param ( # String to decode [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [string]$InputString ) PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Decoding string from HTML" [System.Web.HttpUtility]::HtmlDecode($InputString) } } function ConvertFrom-URLEncoded { <# .SYNOPSIS Decode a URL encoded string #> [CmdletBinding()] [OutputType([String])] param ( # String to decode [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [string]$InputString ) PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Decoding string from URL" [System.Web.HttpUtility]::UrlDecode($InputString) } } function ConvertTo-Attachment { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Attachment] )] param ( # object to convert [Parameter( ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Attachment" if($_.container.id) { $PageId = $_.container.id } else { $PageID = $_._expandable.container -replace '^.*\/content\/', '' $PageID = [convert]::ToInt32($PageID, 10) } [ConfluencePS.Attachment](ConvertTo-Hashtable -InputObject ($object | Select-Object ` @{Name = "id"; Expression = { $ID = $_.id -replace 'att', '' [convert]::ToInt32($ID, 10) } }, status, title, @{Name = "filename"; Expression = { '{0}_{1}' -f $PageID, $_.title | Remove-InvalidFileCharacter } }, @{Name = "mediatype"; Expression = { $_.extensions.mediaType } }, @{Name = "filesize"; Expression = { [convert]::ToInt32($_.extensions.fileSize, 10) } }, @{Name = "comment"; Expression = { $_.extensions.comment } }, @{Name = "spacekey"; Expression = { $_._expandable.space -replace '^.*\/space\/', '' } }, @{Name = "pageid"; Expression = { $PageID } }, @{Name = "version"; Expression = { if ($_.version) { ConvertTo-Version $_.version } else {$null} } }, @{Name = "URL"; Expression = { $base = $_._links.base if (!($base)) { $base = $_._links.self -replace '\/rest.*', '' } if ($_._links.download) { "{0}{1}" -f $base, $_._links.download } else {$null} } } )) } } } function ConvertTo-GetParameter { <# .SYNOPSIS Generate the GET parameter string for an URL from a hashtable #> [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [hashtable]$InputObject ) BEGIN { [string]$parameters = "?" } PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Making HTTP get parameter string out of a hashtable" foreach ($key in $InputObject.Keys) { $parameters += "$key=$($InputObject[$key])&" } } END { $parameters -replace ".$" } } function ConvertTo-HashTable { <# .SYNOPSIS Converts a PSCustomObject to Hashtable .DESCRIPTION PowerShell v4 on Windows 8.1 seems to have trouble casting [PSCustomObject] to custom classes. This function is a workaround, as casting from [Hashtable] is no problem. #> param( # Object to convert [Parameter(Mandatory = $true)] [PSCustomObject]$InputObject ) begin { $hash = @{} $InputObject.PSObject.properties | Foreach-Object { $hash[$_.Name] = $_.Value } Write-Output $hash } } function ConvertTo-HTMLEncoded { <# .SYNOPSIS Encode a string into HTML (eg: > instead of >) #> [CmdletBinding()] [OutputType([String])] param ( # String to encode [Parameter( Position = $true, Mandatory = $true, ValueFromPipeline = $true )] [string]$InputString ) PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Encoding string to HTML" [System.Web.HttpUtility]::HtmlEncode($InputString) } } function ConvertTo-Icon { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Icon] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Icon" [ConfluencePS.Icon](ConvertTo-Hashtable -InputObject ($object | Select-Object ` Path, Width, Height, IsDefault )) } } } function ConvertTo-Label { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Version] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Label" [ConfluencePS.Label](ConvertTo-Hashtable -InputObject ($object | Select-Object ` id, name, prefix )) } } } function ConvertTo-Page { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Page] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Page" [ConfluencePS.Page](ConvertTo-Hashtable -InputObject ($object | Select-Object ` id, status, title, @{Name = "space"; Expression = { if ($_.space) { ConvertTo-Space $_.space } else {$null} } }, @{Name = "version"; Expression = { if ($_.version) { ConvertTo-Version $_.version } else {$null} } }, @{Name = "body"; Expression = {$_.body.storage.value}}, @{Name = "ancestors"; Expression = { if ($_.ancestors) { ConvertTo-PageAncestor $_.ancestors } else {$null} } }, @{Name = "URL"; Expression = { $base = $_._links.base if (!($base)) { $base = $_._links.self -replace '\/rest.*', '' } if ($_._links.webui) { "{0}{1}" -f $base, $_._links.webui } else {$null} } }, @{Name = "ShortURL"; Expression = { $base = $_._links.base if (!($base)) { $base = $_._links.self -replace '\/rest.*', '' } if ($_._links.tinyui) { "{0}{1}" -f $base, $_._links.tinyui } else {$null} } } )) } } } function ConvertTo-PageAncestor { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Page] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Page (Ancestor)" [ConfluencePS.Page](ConvertTo-Hashtable -InputObject ($object | Select-Object ` id, status, title )) } } } function ConvertTo-Space { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Space] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Space" [ConfluencePS.Space](ConvertTo-Hashtable -InputObject ($object | Select-Object ` id, key, name, @{Name = "description"; Expression = {$_.description.plain.value}}, @{Name = "Icon"; Expression = { if ($_.icon) { ConvertTo-Icon $_.icon } else {$null} } }, type, @{Name = "Homepage"; Expression = { if ($_.homepage -is [PSCustomObject]) { ConvertTo-Page $_.homepage } else {$null} # homepage might be a string }} )) } } } function ConvertTo-URLEncoded { <# .SYNOPSIS Encode a string into URL (eg: %20 instead of " ") #> [CmdletBinding()] [OutputType([String])] param ( # String to encode [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [string]$InputString ) PROCESS { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Encoding string to URL" [System.Web.HttpUtility]::UrlEncode($InputString) } } function ConvertTo-User { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.User] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to User" [ConfluencePS.User](ConvertTo-Hashtable -InputObject ($object | Select-Object ` username, userKey, @{Name = "profilePicture"; Expression = { ConvertTo-Icon $_.profilePicture }}, displayname )) } } } function ConvertTo-Version { <# .SYNOPSIS Extracted the conversion to private function in order to have a single place to select the properties to use when casting to custom object type #> [CmdletBinding()] [OutputType( [ConfluencePS.Version] )] param ( # object to convert [Parameter( Position = 0, ValueFromPipeline = $true )] $InputObject ) Process { foreach ($object in $InputObject) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Converting Object to Version" [ConfluencePS.Version](ConvertTo-Hashtable -InputObject ($object | Select-Object ` @{Name = "by"; Expression = { ConvertTo-User $_.by }}, when, friendlyWhen, number, message, minoredit )) } } } function Invoke-WebRequest { # For Version up to 5.1 [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=217035')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( "PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification = "Converting received plaintext token to SecureString" )] param( [switch] ${UseBasicParsing}, [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [uri] ${Uri}, [Microsoft.PowerShell.Commands.WebRequestSession] ${WebSession}, [Alias('SV')] [string] ${SessionVariable}, [pscredential] [System.Management.Automation.CredentialAttribute()] ${Credential}, [switch] ${UseDefaultCredentials}, [ValidateNotNullOrEmpty()] [string] ${CertificateThumbprint}, [ValidateNotNull()] [System.Security.Cryptography.X509Certificates.X509Certificate] ${Certificate}, [string] ${UserAgent}, [switch] ${DisableKeepAlive}, [ValidateRange(0, 2147483647)] [int] ${TimeoutSec}, [System.Collections.IDictionary] ${Headers}, [ValidateRange(0, 2147483647)] [int] ${MaximumRedirection}, [Microsoft.PowerShell.Commands.WebRequestMethod] ${Method}, [uri] ${Proxy}, [pscredential] [System.Management.Automation.CredentialAttribute()] ${ProxyCredential}, [switch] ${ProxyUseDefaultCredentials}, [Parameter(ValueFromPipeline = $true)] [System.Object] ${Body}, [string] ${ContentType}, [ValidateSet('chunked', 'compress', 'deflate', 'gzip', 'identity')] [string] ${TransferEncoding}, [string] ${InFile}, [string] ${OutFile}, [switch] ${PassThru}) begin { if ($Credential) { $SecureCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes( $('{0}:{1}' -f $Credential.UserName, $Credential.GetNetworkCredential().Password) )) $PSBoundParameters["Headers"]["Authorization"] = "Basic $($SecureCreds)" $null = $PSBoundParameters.Remove("Credential") } if ($InFile) { $boundary = [System.Guid]::NewGuid().ToString() $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1") $fileName = Split-Path -Path $InFile -Leaf $readFile = Get-Content -Path $InFile -Encoding Byte $fileEnc = $enc.GetString($readFile) $PSBoundParameters["Body"] = @' --{0} Content-Disposition: form-data; name="file"; filename="{1}" Content-Type: application/octet-stream {2} --{0}-- '@ -f $boundary, $fileName, $fileEnc $PSBoundParameters["Headers"]['X-Atlassian-Token'] = 'nocheck' $PSBoundParameters["ContentType"] = "multipart/form-data; boundary=`"$boundary`"" $null = $PSBoundParameters.Remove("InFile") } try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-WebRequest', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } <# .ForwardHelpTargetName Microsoft.PowerShell.Utility\Invoke-WebRequest .ForwardHelpCategory Cmdlet #> } if ($PSVersionTable.PSVersion.Major -ge 6) { function Invoke-WebRequest { #require -Version 6 [CmdletBinding(DefaultParameterSetName = 'StandardMethod', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=217035')] param( [switch] ${UseBasicParsing}, [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [uri] ${Uri}, [Microsoft.PowerShell.Commands.WebRequestSession] ${WebSession}, [Alias('SV')] [string] ${SessionVariable}, [switch] ${AllowUnencryptedAuthentication}, [Microsoft.PowerShell.Commands.WebAuthenticationType] ${Authentication}, [pscredential] [System.Management.Automation.CredentialAttribute()] ${Credential}, [switch] ${UseDefaultCredentials}, [ValidateNotNullOrEmpty()] [string] ${CertificateThumbprint}, [ValidateNotNull()] [X509Certificate] ${Certificate}, [switch] ${SkipCertificateCheck}, [Microsoft.PowerShell.Commands.WebSslProtocol] ${SslProtocol}, [securestring] ${Token}, [string] ${UserAgent}, [switch] ${DisableKeepAlive}, [ValidateRange(0, 2147483647)] [int] ${TimeoutSec}, [System.Collections.IDictionary] ${Headers}, [ValidateRange(0, 2147483647)] [int] ${MaximumRedirection}, [Parameter(ParameterSetName = 'StandardMethod')] [Parameter(ParameterSetName = 'StandardMethodNoProxy')] [Microsoft.PowerShell.Commands.WebRequestMethod] ${Method}, [Parameter(ParameterSetName = 'CustomMethod', Mandatory = $true)] [Parameter(ParameterSetName = 'CustomMethodNoProxy', Mandatory = $true)] [Alias('CM')] [ValidateNotNullOrEmpty()] [string] ${CustomMethod}, [Parameter(ParameterSetName = 'CustomMethodNoProxy', Mandatory = $true)] [Parameter(ParameterSetName = 'StandardMethodNoProxy', Mandatory = $true)] [switch] ${NoProxy}, [Parameter(ParameterSetName = 'StandardMethod')] [Parameter(ParameterSetName = 'CustomMethod')] [uri] ${Proxy}, [Parameter(ParameterSetName = 'StandardMethod')] [Parameter(ParameterSetName = 'CustomMethod')] [pscredential] [System.Management.Automation.CredentialAttribute()] ${ProxyCredential}, [Parameter(ParameterSetName = 'StandardMethod')] [Parameter(ParameterSetName = 'CustomMethod')] [switch] ${ProxyUseDefaultCredentials}, [Parameter(ValueFromPipeline = $true)] [System.Object] ${Body}, [string] ${ContentType}, [ValidateSet('chunked', 'compress', 'deflate', 'gzip', 'identity')] [string] ${TransferEncoding}, [string] ${InFile}, [string] ${OutFile}, [switch] ${PassThru}, [switch] ${PreserveAuthorizationOnRedirect}, [switch] ${SkipHeaderValidation}) begin { if ($Credential -and (-not ($Authentication))) { $PSBoundParameters["Authentication"] = "Basic" } if ($InFile) { $multipartContent = [System.Net.Http.MultipartFormDataContent]::new() $FileStream = [System.IO.FileStream]::new($InFile, [System.IO.FileMode]::Open) $fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data") $fileHeader.Name = "file" $fileHeader.FileName = ([System.io.FileInfo]$InFile).name $fileContent = [System.Net.Http.StreamContent]::new($FileStream) $fileContent.Headers.ContentDisposition = $fileHeader $fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream") $multipartContent.Add($fileContent) $PSBoundParameters["Headers"]['X-Atlassian-Token'] = 'nocheck' $PSBoundParameters["Body"] = $multipartContent $null = $PSBoundParameters.Remove("InFile") } try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-WebRequest', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } <# .ForwardHelpTargetName Microsoft.PowerShell.Utility\Invoke-WebRequest .ForwardHelpCategory Cmdlet #> } } function Remove-InvalidFileCharacter { <# .SYNOPSIS Replace any invalid filename characters from a string with underscores #> [CmdletBinding( ConfirmImpact = 'Low', SupportsShouldProcess = $false )] [OutputType( [String] )] [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')] param ( # string to process [Parameter( ValueFromPipeline = $true )] [String]$InputString ) BEGIN { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" $InvalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' $RegExInvalid = "[{0}]" -f [RegEx]::Escape($InvalidChars) } Process { foreach ($_string in $InputString) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Removing invalid characters" $_string -replace $RegExInvalid, '_' } } } function Test-Captcha { [CmdletBinding()] param( # Response of Invoke-WebRequest [Parameter( ValueFromPipeline = $true )] [PSObject]$InputObject ) begin { $tokenRequiresCaptcha = "AUTHENTICATION_DENIED" $headerRequiresCaptcha = "X-Seraph-LoginReason" } process { if ($InputObject.Headers -and $InputObject.Headers[$headerRequiresCaptcha]) { if ( ($InputObject.Headers[$headerRequiresCaptcha] -split ",") -contains $tokenRequiresCaptcha ) { Write-Warning "Confluence requires you to log on to the website before continuing for security reasons." } } } end { } } function Write-DebugMessage { [CmdletBinding()] param( [Parameter( ValueFromPipeline = $true )] $Message ) begin { $oldDebugPreference = $DebugPreference if (!($DebugPreference -eq "SilentlyContinue")) { $DebugPreference = 'Continue' } } process { Write-Debug $Message } end { $DebugPreference = $oldDebugPreference } } |