XWikiAutomation.psm1

# Copyright WebMD Health Services
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License

#Requires -Version 5.1
Set-StrictMode -Version 'Latest'

# Functions should use $moduleRoot as the relative root from which to find
# things. A published module has its function appended to this file, while a
# module in development has its functions in the Functions directory.
$moduleRoot = $PSScriptRoot

# Store each of your module's functions in its own file in the Functions
# directory. On the build server, your module's functions will be appended to
# this file, so only dot-source files that exist on the file system. This allows
# developers to work on a module without having to build it first. Grab all the
# functions that are in their own files.
$functionsPath = Join-Path -Path $moduleRoot -ChildPath 'Functions\*.ps1'
if( (Test-Path -Path $functionsPath) )
{
    foreach( $functionPath in (Get-Item $functionsPath) )
    {
        . $functionPath.FullName
    }
}



function Get-XWPage
{
    <#
    .SYNOPSIS
    Gets all pages inside of a space.
 
    .DESCRIPTION
    The `Get-XWPage` function gets all of the content for all of the pages in a given space. Provide the path to the
    space as the `SpacePath` parameter.
 
    To get the details on a specific page rather than all pages inside of a space, provide the name of the page to the
    `Name` parameter.
 
    .EXAMPLE
    Get-XWPage -Session $session -SpacePath 'foo', 'bar'
 
    Demonstrates getting all of the pages within the space 'foo/bar'.
 
    .EXAMPLE
    Get-XWPage -Session $session -SpacePath 'Main' -Name 'MyPage'
 
    Demonstrates getting the page named 'MyPage'inside of the space named 'Main'.
 
    .EXAMPLE
    Get-XWPage -Session $session -SpacePath 'scooby', 'doo' -WikiName 'cartoons'
 
    Demonstrates getting all of the pages within the space 'scooby/doo' inside of the wiki named 'cartoons'.
    #>

    [CmdletBinding()]
    param(
        # The Session object for an XWiki session. Create a new Session using `New-XWSession`.
        [Parameter(Mandatory)]
        [Object] $Session,

        # The space path to get to the page.
        [Parameter(Mandatory)]
        [String[]] $SpacePath,

        # The name of the page.
        [String] $Name,

        # The name of the wiki the page belongs to. Defaults to xwiki.
        [String] $WikiName = 'xwiki'
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    $path = "wikis/${WikiName}/spaces/$($SpacePath -join '/spaces/')/pages"
    Write-Debug $path
    if ($Name)
    {
        $path = "${path}/${Name}"
    }

    $res = Invoke-XWRestMethod -Session $Session -Name $path -AsJson
    if (-not $Name)
    {
        $res = $res | Select-Object -ExpandProperty 'pageSummaries'
    }
    return $res | Select-Object -ExcludeProperty 'links'
}


function Get-XWWikis
{
    <#
    .SYNOPSIS
    Lists the wikis on an XWiki instance.
 
    .DESCRIPTION
    The `Get-XWikis` function gets all of the parent wikis within an XWiki instance. On most XWikis, this should just
    return an XWiki named `xwiki`.
 
    .EXAMPLE
    Get-XWWikis -Session $Session
 
    Demonstrates listing all of the wikis on an XWiki instance.
    #>

    [CmdletBinding()]
    param(
        # The Session object for an XWiki session. Create a new Session using `New-XWSession`.
        [Parameter(Mandatory)]
        [pscustomobject] $Session
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    Invoke-XWRestMethod -Session $Session -Name 'wikis' -AsJson |
        Select-Object -ExpandProperty 'wikis' |
        Write-Output
}


function Invoke-XWRestMethod
{
    <#
    .SYNOPSIS
    Invokes an XWiki Rest endpoint.
 
    .DESCRIPTION
    The `Invoke-XWRestMethod` invokes a XWiki REST API method. You pass the path to the endpoint (everything after
    `/xwiki/rest`) via the `Name` parameter, the HTTP method to use via the `Method` parameter, and the parameters to
    pass in the body of the request via the `Parameter` parameter. XWiki allows for returning REST API requests in both
    JSON and XML formats. The default return format is XML, to change this to JSON use the `AsJson` switch.
 
    When trying to update or create data on the XWiki server, it is recommended to use PUT requests rather than POST
    requests. This is due to the way XWiki handles requests.
 
    When using the `WhatIf` parameter, only web requests that use the `Get` HTTP method are made.
 
    .EXAMPLE
    Invoke-XWRestMethod -Session $session -Name 'wikis'
 
    Demonstrates using the `xwiki/rest/wikis` endpoint with a GET request to find a list of all of the wikis available.
    .LINK
    https://www.xwiki.org/xwiki/bin/view/Documentation/UserGuide/Features/XWikiRESTfulAPI
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='NoBody')]
    param(
        # The Session object for an XWiki session. Create a new Session using `New-XWSession`.
        [Parameter(Mandatory)]
        [pscustomobject] $Session,

        # The name of the API endpoint to make a request to. This should be everything after the `/xwiki/rest/` in the URL.
        [Parameter(Mandatory)]
        [String] $Name,

        # The REST method to use when making a request. Defaults to GET.
        [Microsoft.PowerShell.Commands.WebRequestMethod] $Method =
            [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,

        # Sets the content to be returned as JSON rather than returned as XML.
        [switch] $AsJson,

        # The body of the request. This is used for POST and PUT requests.
        [Parameter(Mandatory, ParameterSetName='Body')]
        [Object] $Body,

        # The content type of the body. Defaults to `application/xml`.
        [Parameter(ParameterSetName='Body')]
        [String] $ContentType = 'application/xml',

        # The form data to send with the request. This is used for POST and PUT requests.
        [Parameter(ParameterSetName='Form')]
        [hashtable] $Form
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $url = [uri]::EscapeUriString("$($Session.Url)rest/${name}")
    if ($AsJson)
    {
        $url = "${url}?media=json"
    }

    $requestParams = @{}

    if ($Body)
    {
        $requestParams['Body'] = $Body
        $requestParams['ContentType'] = $ContentType
    }

    if ($Form)
    {
        $requestParams['Body'] = $Form
        $requestParams['ContentType'] = 'application/x-www-form-urlencoded'
    }

    Write-Debug "${method} - ${url}"
    foreach ($key in $requestParams.Keys)
    {
        if ($key -ne 'Form')
        {
            Write-Debug "${key}: $($requestParams[$key])"
        }
        else
        {
            foreach ($formKey in $requestParams[$key].Keys)
            {
                Write-Debug " ${formKey}: $($requestParams[$key][$formKey])"
            }
        }
    }

    $auth = "$($Session.Credential.UserName):$($Session.Credential.GetNetworkCredential().Password)"
    $bytes = [Text.Encoding]::ASCII.GetBytes($auth)
    $base64AuthInfo = [Convert]::ToBase64String($bytes)

    $headers = @{
        Authorization="Basic $base64AuthInfo"
    }

    if ($Session.Url.ToString().Contains('xwikiplayground'))
    {
    }

    try
    {
        if ($Method -eq [Microsoft.PowerShell.Commands.WebRequestMethod]::Get -or $PSCmdlet.ShouldProcess($url, $method))
        {
            Invoke-RestMethod -Headers $headers -Method $Method -Uri $url @requestParams -UseBasicParsing |
                ForEach-Object { $_ } |
                Where-Object { $_ } |
                Write-Output
        }
    }
    catch
    {
        $Global:Error.RemoveAt(0)
    }
}


function New-XWSession
{
    <#
    .SYNOPSIS
    Creates a session object used to communicate with an XWiki instance.
 
    .DESCRIPTION
    The `New-BMSession` function creates and returns a session object that is required by any function in the
    XWikiAutomation module that communicates with XWiki. The session includes XWiki's URL and the
    credentials to use when making requests to XWiki's APIs.
 
    .EXAMPLE
    $session = New-XWSession -Url 'https://xwiki.com' -Credential $credential
 
    Demonstrates how to call `New-XWSession`. In this case, the returned session object can be passed to other
    BuildMasterAutomation module functions to communicate with XWiki at `https://xwiki.com` with the
    credential in `$credential`.
    #>

    [CmdletBinding()]
    param(
        # The URL to the XWiki instance to use.
        [Parameter(Mandatory)]
        [Uri] $Url,

        # The username and password to use when making requests to XWiki.
        [Parameter(Mandatory)]
        [pscredential] $Credential
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    if (-not $Url.AbsolutePath.EndsWith('/'))
    {
        $Url = [uri]::new($Url, '/')
    }

    return [pscustomobject]@{
        Url = $Url;
        Credential = $Credential;
    }
}


function Remove-XWPage
{
    <#
    .SYNOPSIS
    Deletes an XWiki page.

    .DESCRIPTION
    The `Remove-XWPage` function deletes an XWiki page. Provide the path to the space and the name of the page to
    delete. If the page does not exist, no action is taken.

    .EXAMPLE
    Remove-XWPage -Session $session -SpacePath 'Main' -Name 'HomePage'

    Demonstrates deleting the page named 'HomePage' in the space 'Main'.

    .EXAMPLE
    Remove-XWPage -Session $session -SpacePath 'Main' -Name 'Snoopy' -WikiName 'cartoons'

    Demonstrates deleting the page named 'Snoopy' in the space 'Main' in the wiki named 'cartoons'.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # The Session object for an XWiki session. Create a new Session using `New-XWSession`.
        [Parameter(Mandatory)]
        [Object] $Session,

        # The space path to get to the page.
        [Parameter(Mandatory)]
        [String[]] $SpacePath,

        # The name of the page.
        [Parameter(Mandatory)]
        [String] $Name,

        # The name of the wiki the page belongs to. Defaults to xwiki.
        [String] $WikiName = 'xwiki'
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $path = "wikis/${WikiName}/spaces/$($SpacePath -join '/spaces/')/pages/${Name}"

    Invoke-XWRestMethod -Session $Session -Name $path -Method Delete
}


function Set-XWPage
{
    <#
    .SYNOPSIS
    Creates or edits an XWiki page.
 
    .DESCRIPTION
    The `Set-XWPage` function creates or edits an XWiki page. It can set the title, parent page, hidden status, and
    the content of the page. If the page already exists, it will be updated with the new information. Only the fields
    that are edited need to be provided.
 
    .EXAMPLE
    Set-XWPage -Session $session -SpacePath 'Main' -Name 'HomePage' -Title 'XWiki Home'
 
    Demonstrates creating or editing a page named 'HomePage' in the space 'Main' with the title 'XWiki Home'.
 
    .EXAMPLE
    Set-XWPage -Session $session -SpacePath 'Main' -Name 'HomePage' -Content 'This is the content of the page.'
 
    Demonstrates creating or editing a page named 'HomePage' in the space 'Main' with the content 'This is the content
    of the page.'
 
    .EXAMPLE
    Set-XWPage -Session $session -SpacePath 'Main' -Name 'HomePage' -Hidden $true
 
    Demonstrates creating or editing a page named 'HomePage' in the space 'Main' and hiding it.
 
    .EXAMPLE
    Set-XWPage -Session $session -SpacePath 'Main' -Name 'HomePage' -Parent 'ParentPage'
 
    Demonstrates creating or editing a page named 'HomePage' in the space 'Main' with the parent page 'ParentPage'. If
    the page already exists, it will be moved to be a child of 'ParentPage'.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')]
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ByAttribute')]
    param (
        # The Session object for an XWiki session. Create a new Session using `New-XWSession`.
        [Parameter(Mandatory)]
        [Object] $Session,

        # The space path to get to the page.
        [Parameter(Mandatory)]
        [String[]] $SpacePath,

        # The name of the page to edit.
        [Parameter(Mandatory)]
        [String] $Name,

        # The new title of the page.
        [Parameter(ParameterSetName='ByAttribute')]
        [String] $Title,

        # The new parent page of this page.
        [Parameter(ParameterSetName='ByAttribute')]
        [String] $Parent,

        # Whether the page should be hidden or not.
        [Parameter(ParameterSetName='ByAttribute')]
        [bool] $Hidden,

        # The new content of the page.
        [Parameter(ParameterSetName='ByAttribute')]
        [String] $Content,

        # An XML object representing the page.
        [Parameter(ParameterSetName='ByXML')]
        [xml] $Body,

        # The name of the wiki the page belongs to. Defaults to xwiki.
        [String] $WikiName = 'xwiki'
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $path = "wikis/${WikiName}/spaces/$($SpacePath -join '/spaces/')/pages/${Name}"

    if ($PSCmdlet.ParameterSetName -eq 'ByXML')
    {
        return Invoke-XWRestMethod -Session $Session -Name $path -Method Put -Body $body.ToString()
    }

    $formData = @{}

    if ($Title)
    {
        $formData['title'] = $Title
    }
    if ($Parent)
    {
        $formData['parent'] = $Parent
    }
    if ($null -ne $Hidden)
    {
        $formData['hidden'] = $Hidden
    }
    if ($Content)
    {
        $formData['content'] = $Content
    }

    Invoke-XWRestMethod -Session $session -Name $path -Method Put -Form $formData |
        Select-Object -ExpandProperty 'page'
}


function Use-CallerPreference
{
    <#
    .SYNOPSIS
    Sets the PowerShell preference variables in a module's function based on the callers preferences.
 
    .DESCRIPTION
    Script module functions do not automatically inherit their caller's variables, including preferences set by common
    parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't
    get passed into any function that belongs to a module.
 
    When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the
    function's caller:
 
     * ErrorAction
     * Debug
     * Confirm
     * InformationAction
     * Verbose
     * WarningAction
     * WhatIf
     
    This function should be used in a module's function to grab the caller's preference variables so the caller doesn't
    have to explicitly pass common parameters to the module function.
 
    This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d).
 
    There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that
    causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add
    explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get
    fixed.
 
    .LINK
    about_Preference_Variables
 
    .LINK
    about_CommonParameters
 
    .LINK
    https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
 
    .LINK
    http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/
 
    .EXAMPLE
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
 
    Demonstrates how to set the caller's common parameter preference variables in a module function.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        #[Management.Automation.PSScriptCmdlet]
        # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]`
        # attribute.
        $Cmdlet,

        [Parameter(Mandatory)]
        # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the
        # `[CmdletBinding()]` attribute.
        #
        # Used to set variables in its callers' scope, even if that caller is in a different script module.
        [Management.Automation.SessionState]$SessionState
    )

    Set-StrictMode -Version 'Latest'

    # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken
    # from about_CommonParameters).
    $commonPreferences = @{
                              'ErrorActionPreference' = 'ErrorAction';
                              'DebugPreference' = 'Debug';
                              'ConfirmPreference' = 'Confirm';
                              'InformationPreference' = 'InformationAction';
                              'VerbosePreference' = 'Verbose';
                              'WarningPreference' = 'WarningAction';
                              'WhatIfPreference' = 'WhatIf';
                          }

    foreach( $prefName in $commonPreferences.Keys )
    {
        $parameterName = $commonPreferences[$prefName]

        # Don't do anything if the parameter was passed in.
        if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) )
        {
            continue
        }

        $variable = $Cmdlet.SessionState.PSVariable.Get($prefName)
        # Don't do anything if caller didn't use a common parameter.
        if( -not $variable )
        {
            continue
        }

        if( $SessionState -eq $ExecutionContext.SessionState )
        {
            Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
        }
        else
        {
            $SessionState.PSVariable.Set($variable.Name, $variable.Value)
        }
    }
}