Common.ps1
Set-StrictMode -Version 2 function New-CommunityCredential { <# .Synopsis Creates a new Community Credential .Description The New-CommunityCredential cmdlet creates a new Credential to use when connecting to a Telligent Evolution community via REST. This credential contains the core infomration needed for all REST requests and needs to be passed to all REST calls. When connecting to a site behind Windows or Basic authentication, the HttpCredentials Parameter should be provided to provide these credentials. Before creating the Credentials, a test connection will be made to the community to validate the credentials. If this connection fails, then an error will occur. This check can be bypassed by specifying the -Force flag. .Parameter CommunityRoot The root url for your Telligent Evolution community .Parameter UserName The username of the user to connect to the community as .Parameter ApiKey The Api Key of the user connecting to the community .Parameter HttpCredentials Specifies the HTTP Credentials to use when connecting to a community behind Windows or Basic authentication. .Parameter Force If specified, the credentials will be created without being validated .Example New-CommunityCredential http://mycommunity.com/ admin abc123 Create a basic Community Credential .Example New-CommunityCredential http://mycommunity.com/ admin abc123 (Get-Credential) Create an Community Credential for a community secured by Windows authentication. You will be prompted for your password at the PowerShell prompt by the Get-Credential command #> [CmdletBinding()] param( [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [alias("Url")] [alias("Root")] [Uri] $CommunityRoot, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $UserName, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string] $ApiKey, [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [PSCredential] $HttpCredentials, [switch] $Force ) $creds = New-Object CommunityCredential @($CommunityRoot, $UserName, $ApiKey, $HttpCredentials) if ($Force -or (Test-CommunityCredential $creds)) { Write-Output $creds } } function Test-CommunityCredential { [CmdletBinding()] param( [parameter(Mandatory=$true, ValueFromPipeline=$true)] [CommunityCredential] $Credential ) $initialErrorCount = $Error.Count Write-Progress 'Validating Credentials' 'Connecting to Info endpoint' $result = Invoke-CommunityRestRequest api.ashx/v2/info.json GET $Credential Write-Progress 'Validating Credentials' 'Connecting to Info endpoint' -Completed return $result -and $Error.Count -eq $initialErrorCount } function Expand-ItemWithSingleProperty { [CmdletBinding()] param( [parameter(Mandatory=$true, ValueFromPipeline=$true)] $Item ) if ($Item){ $properties = $Item | Get-Member -MemberType NoteProperty if (!($properties -is [array])) { $propertyName = $properties.Name if ($propertyName -eq '*') { #If Select-Object excludes all properties, it returns an object with an empty * Parameter # We want to treat this as no output so do nothing } else { Write-Verbose "Expanding Property: $propertyName" $Item = $Item | select -ExpandProperty $propertyName $Item.psobject.TypeNames.Insert(0, $propertyName) } } $Item } } function ConvertTo-MimeMultiPartBody { param( [Parameter(Mandatory=$true)] [string]$Boundary, [Parameter(Mandatory=$true)] [hashtable]$Data, [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8 ) $body = ""; $Data.GetEnumerator() |% { $name = $_.Key $value = $_.Value $body += "--$Boundary`r`n" $body += "Content-Disposition: form-data; name=`"$name`"" if ($value -is [byte[]]) { $fileName = $Data['FileName'] if(!$fileName) { $fileName = $name } $body += "; filename=`"$fileName`"`r`n" $body += "Content-Type: application/octet-stream" #ISO-8859-1 is only encoding where byte value == code point value $value = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($value) } $body += "`r`n`r`n" $body += $value $body += "`r`n" } $body += "--$boundary--" $body } function Write-RestErrors { param( $response ) if ($response) { $response.Errors |% { Write-Error -Message $_ } $response.Warnings |% { Write-Warning -Message $_ } #Can't write-Host via remoting / workflows #Hate the Try/Catch but can't find a better way to detect try{ $response.Info |% { Write-Host $_ -ErrorAction SilentlyContinue }} catch {} } } function Invoke-CommunityRestRequest { [CmdletBinding(SupportsShouldProcess = $true)] param( [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string]$Endpoint, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateSet('GET','POST', 'PUT', 'DELETE')] [string]$Method, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [CommunityCredential]$Credential, [parameter(Mandatory=$false, ValueFromPipeline=$true)] [AllowEmptyCollection()] [hashtable]$Parameter, [string]$Impersonate ) process { $endpointUri = $Credential.Root.AbsoluteUri.TrimEnd('/') + '/' + $Endpoint.TrimStart('/') $headers = @{ 'Rest-User-Token' = [Convert]::ToBase64String([System.Text.Encoding]::Utf8.GetBytes("$($Credential.ApiKey):$($Credential.Username)")) } if ($method -eq 'PUT' -or $method -eq 'DELETE'){ $headers['Rest-Method'] = $Method } if ($Impersonate) { $headers['Rest-Impersonate-User'] = $Impersonate } $splat = @{} if($Parameter) { $body = @{} $containsFileData = $false; $Parameter.GetEnumerator() |% { if($_.Value -is [byte[]]) { $containsFileData = $true; $body[$_.Key] = $_.Value } elseif ($_.Value -is [Array]) { $body[$_.Key] = $_.Value -Join ',' } elseif($_.Value -is [Hashtable]) { $name = $_.Key $_.Value.GetEnumerator() |% { $key = $_.key $body["_${name}_${key}"] = $_.Value } } else { $body[$_.Key] = $_.Value } } if ($containsFileData) { $boundary = [Guid]::NewGuid().ToString('N') $splat.ContentType = "multipart/form-data; boundary=$boundary" $body = ConvertTo-MimeMultiPartBody -Boundary $boundary -Data $body } $paramJson = $Parameter | ConvertTo-Json -Compress } else { $body = $null $paramJson = "{}" } #Don't actually submit as json, but use for -WhatIf and -Verbose messages if ($method -ne 'GET' -and -not $PSCmdlet.ShouldProcess($endpointUri, "${method}: $paramJson")) { return } Write-Verbose "$method $Endpoint $paramJson (User: '$($Credential.Username)'$(if($Impersonate){ "Impersonate: '$Impersonate'"}))" $response = $null try { if ($Credential.HttpCredential) { $splat.Credential = $Credential.HttpCredential } # Hide progress from Invoke-Web Request $progressPreference = 'silentlyContinue' $response = Invoke-RestMethod -Uri $endpointUri ` -Headers $headers ` -Body $body ` -MaximumRedirection 0 ` -Method $(if ($Method -eq 'GET') { 'GET' } else { 'POST'}) ` -UserAgent 'Telligent Community Powershell REST Client' ` @splat } catch [System.Net.WebException] { try { #Due to .net stupidity, any non 200 status codes throw an exception $httpResponse = [System.Net.HttpWebResponse]$_.Exception.Response if (!$httpResponse) { Write-Error $_ } else { $responseStream = $httpResponse.GetResponseStream() $responseStream.Seek(0, 'Begin') | Out-Null $reader = new-object System.IO.StreamReader $responseStream $content = $reader.ReadToEnd() #If response is JSON, try to get errors from Errors element try { $response = $content | ConvertFrom-Json } catch {} if (!($response -and $response.Errors)) { Write-Error $_ } } } finally { if($reader) { $reader.Dispose() } if ($responseStream) { $responseStream.Dispose() } if ($httpResponse) { $httpResponse.Dispose() } } } if ($response) { Write-RestErrors $response if (!$response.Errors) { # Make response objects easier to deal with by excluding Errors, Warnings and Info # 99% of the time, these won't be needed programatically. # If they are, the ErrorVariable & WarningVariable common parameters can be used $response | select * -ExcludeProperty Errors, Warnings, Info | Expand-ItemWithSingleProperty } } } } function Invoke-CommunityRestPagedRequest { [CmdletBinding(SupportsPaging=$true, SupportsShouldProcess = $true)] param( [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string]$Endpoint, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateSet('GET','POST', 'PUT', 'DELETE')] [string]$Method, [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [CommunityCredential]$Credential, [parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [AllowEmptyCollection()] [hashtable]$Parameter, [string]$Impersonate ) $firstItem = $PSCmdlet.PagingParameters.Skip $lastItem = $firstItem + $PSCmdlet.PagingParameters.First $pageSize = [Math]::Min($PSCmdlet.PagingParameters.First, 100) $firstPage = [Math]::Floor($firstItem / $pageSize) $lastPage = [Math]::Ceiling($lastItem / $pageSize) - 1 $skipStart = $firstItem - ($firstPage * $pageSize) Write-Verbose "Retrieving Items $firstItem - $lastItem" if (!$Parameter) { $Parameter= @{} } $Parameter["PageSize"] = $pageSize $totalCount = 0 for($i = $firstPage; $i -le $lastPage; $i++) { Write-Verbose "Retrieving Page $i (PageSize $pageSize)" $Parameter["PageIndex"] = $i $response = Invoke-CommunityRestRequest -Endpoint $Endpoint -Method $Method -Credential $Credential -Parameter $Parameter -Impersonate $Impersonate if($response) { if ($response.PageSize -ne $PageSize) { Write-Warning "Wrong Page Size used (Actual: $($response.PageSize), Expected: $PageSize)" } if ($response.PageIndex -ne $i) { Write-Warning "Wrong Page Index used (Actual: $($response.PageIndex), Expected: $i)" } } #TODO: Need to trim out start & end of boundary #TODO: Ensure when doing this, the PSObject type does not rever to PSCustomObject $response | select * -ExcludeProperty PageSize, PageIndex, TotalCount | Expand-ItemWithSingleProperty #Break early if there are no more pages of data $totalCount = $response.TotalCount if ($totalCount -lt ($i + 1) * $pageSize) { break; } } if ($PSCmdlet.PagingParameters.IncludeTotalCount) { $PSCmdlet.PagingParameters.NewTotalCount($totalCount, 1) } } New-Alias -Name ncc -Value 'New-CommunityCredential' Export-ModuleMember -Function New-CommunityCredential, Test-CommunityCredential, Invoke-CommunityRestRequest, Invoke-CommunityRestPagedRequest |