NabuNetManager.psm1
# script variables... $CurrentContext = @{ Host = $null Token = $null # access token for this current connection... TokenExpires = $null Name = $null } # helper classes first... class NabuUpdateImages { NabuUpdateImages($in){ $this.ConfigImageVersion = $in.ConfigImageVersion $this.ConfigImageAssetId = $in.ConfigImageAsset $this.FirmwareImageVersion = $in.FirmwareImageVersion $this.FirmwareImageAssetId = $in.FirmwareImageAsset } [string]$ConfigImageVersion [string]$FirmwareImageVersion [Nullable[int]]$ConfigImageAssetId [Nullable[int]]$FirmwareImageAssetId } class NabuArticleBase { NabuArticleBase($in) { $this.Title = $in.title $this.Article = $in.article $this.Created = [DateTime]::Parse($in.created) $this.ReferenceDate = $in.referenceDate } [string]$Title [string]$Article [DateTime]$Created [Nullable[DateTime]]$ReferenceDate } class NabuHost { NabuHost($name) { $this.Name = $name } NabuHost($name, $serialized) { $this.Name = $name $this.RemoteBaseUri = $serialized.RemoteBaseUri $this.Registered = $serialized.Registered $this.LastContacted = $serialized.LastContacted $this.Token = $serialized.Token $this.RemoteName = $serialized.RemoteName $this.RemoteTagline = $serialized.RemoteTagline } [string] $Name [string] $RemoteBaseUri [datetime] $Registered [datetime] $LastContacted [string] $Token [string] $RemoteName [string] $RemoteTagline } # public visible server context info class NabuHostInfo { NabuHostInfo($in, $name = $null) { $this.Name = $name ?? $in.Name $this.Uri = $in.RemoteBaseUri $this.LastContacted = $in.LastContacted $this.Registered = $in.Registered $this.HasToken = [bool]$in.Token $this.RemoteName = $in.RemoteName $this.RemoteTagline = $in.RemoteTagline } [string] $Name [string] $Uri [string] $RemoteTagline [string] $RemoteName [DateTime] $LastContacted [DateTime] $Registered [bool] $HasToken } class NabuTemplateInfo { NabuTemplateInfo($in, $name = $null) { $this.Id = $name ?? $in.Id $this.Subject = $in.Subject $this.Body = $in.Body } [ValidatePattern("^[a-zA-Z0-9]+$", ErrorMessage = "Only letters and digits allowed!")] [string] $Id [ValidatePattern("^[^\n\r]+$", ErrorMessage = "May not contain newlines!")] [string] $Subject [string] $Body } # public visible server context info class NabuAccountInfo { NabuAccountInfo($in) { $this.Name = $in.Name $this.IsComplete = [bool]$in.isComplete $this.ContactEMail = $in.contactEMail $this.DisplayName = $in.displayName $this.HighscoreName = $in.highscoreName $this.EnableApiAccess = [bool]$in.enableAPIAccess $this.EnableDeviceConnections = [bool]$in.enableDeviceConnections $this.IsAdministrator = [bool]$in.isAdministrator $this.IsContentManager = [bool]$in.isContentManager $this.IsEnabled = [bool]$in.isEnabled $this.IsModerator = [bool]$in.isModerator } [string] $Name [string] $ContactEMail [string] $DisplayName [string] $HighscoreName [bool] $EnableApiAccess [bool] $EnableDeviceConnections [bool] $IsAdministrator [bool] $IsContentManager [bool] $IsEnabled [bool] $IsModerator [bool] $IsComplete } class NabuAccountSecurity { NabuAccountSecurity($in, $name) { $this.Name = $name $this.Enable2FA = [bool]$in.enable2FA $this.Finished2FASetup = [bool]$in.finished2FASetup $this.ForcePasswordChange = [bool]$in.forcePasswordChange $this.LastValid2FAConfirm = $in.lastValid2FAConfirm ? [DateTime]$in.lastValid2FAConfirm : $null $this.LastValidPasswordLogin = $in.lastValidPasswordLogin ? [DateTime]$in.lastValidPasswordLogin : $null $this.MailValidationExpiration = $in.mailValidationExpiration ? [DateTime]$in.mailValidationExpiration : $null $this.ProposedMailAddress = $in.ProposedMailAddress $this.Started2FASetup = $in.started2FASetup ? [DateTime]$in.started2FASetup : $null } [string] $Name [bool] $Enable2FA [System.Nullable[DateTime]] $Started2FASetup [bool] $Finished2FASetup [bool]$ForcePasswordChange [System.Nullable[DateTime]] $LastValid2FAConfirm [System.Nullable[DateTime]] $LastValidPasswordLogin [System.Nullable[DateTime]] $MailValidationExpiration [string] $ProposedMailAddress } # helper functions next... function Save-RegisteredHost { [CmdletBinding()] param ( [Parameter()] [string]$Name, [Parameter()] [NabuHost]$Data ) $path = Join-Path -Path "~" -ChildPath ".nabunet" if (!(Test-Path -Path $path)) { New-Item -ItemType Directory -Path $path | Out-Null # $data = Import-PowerShellDataFile -Path $path # New-Object NabuHost -ArgumentList $data } $path = Join-Path -Path $path -ChildPath "$Name.xml" $Data | Export-Clixml -Path $path } function Load-RegisteredHost { [CmdletBinding()] param ( [Parameter()] [string] $Name ) $path = Join-Path -Path "~" -ChildPath ".nabunet" $path = Join-Path -Path $path -ChildPath "$Name.xml" if (Test-Path -Path $path) { Import-Clixml -Path $path | ForEach-Object { New-Object NabuHost -ArgumentList $Name, $_ } # | Where-Object { $_ -is [NabuHost] } # $data = Import-PowerShellDataFile -Path $path # New-Object NabuHost -ArgumentList $data } else { throw "Registered host $Name not found! (casing?)" } } function Get-AllRegisteredHost { [CmdletBinding()] param ( ) $path = Join-Path -Path "~" -ChildPath ".nabunet" if (Test-Path -Path $path) { Get-ChildItem -Path (Join-Path -Path $path -ChildPath "*.xml") } } function Call-Napi { [CmdletBinding(PositionalBinding = $false)] param ( [Parameter()] [NabuHost]$Connection = $null, [Parameter(Mandatory = $true)] [string]$Path, [Parameter()] [string]$Version = "v1", [Parameter()] [string]$Method = "GET", [Parameter()] [Object]$Body = $null, [Parameter()] [hashtable]$Query = $null, [Parameter()] [hashtable]$Uri = $null ) if (-not $script:UserAgent) { $script:UserAgent = "NabuNetPS/1 ($($MyInvocation.MyCommand.Module.Version), PS $($PSVersionTable["PSVersion"]) $($PSVersionTable["PSEdition"]) $($PSVersionTable["OS"]))" } $Token = $null if (!$Connection) { $Connection = $script:CurrentContext.Host $Token = $script:CurrentContext.Token $Expires = $script:CurrentContext.TokenExpires if ($null -eq $Token -or ($Expires -lt [DateTime]::UtcNow)) { # token is missing or expired... if ($null -ne $Connection.Token) { Write-Verbose "Access token needed, requesting..." $tmp = Call-Napi -Connection $Connection -Version $Version -Path "login" -Body @{ token = $Connection.Token } -Method "POST" if ($tmp.token) { $script:CurrentContext.Token = $tmp.token $Token = $tmp.token $script:CurrentContext.TokenExpires = [datetime]::Parse($tmp.validUntil).AddSeconds(-10) # safety margin... $Expires = $script:CurrentContext.TokenExpires $tmp = $null } else { $script:CurrentContext.Token = $null $script:CurrentContext.TokenExpires = $null throw "Login call didn't yield a valid token..." } } else { Write-Verbose "Access token needed, but no token provided, can only call anonymously!" } } else { Write-Verbose "Access token present and valid." } } if (!$Connection) { throw "Session is not connected! Use Connect-NabuNetHost first!" } if ($Uri) { foreach($uname in $Uri.Keys) { $Path = $Path -replace "{$uname}", [Uri]::EscapeUriString($Uri[$uname]) } } $RawUri = "$($Connection.RemoteBaseUri)/$Version/$Path" if ($Query) { $sep = "?" foreach ($qname in $Query.Keys) { $RawUri += "$sep$qname=$([Uri]::EscapeDataString($Query[$qname]))" $sep = "&" } } Write-Verbose "Call: URI=$RawUri" $hdr = @{} if ($Token) { $hdr["Authorization"] = "Bearer $Token" Write-Verbose "Authentication set" } if ($Body) { $Body = $Body | ConvertTo-Json Write-Verbose "Body for request: $($Body.Length) characters" if ($Body.Length -lt 100) { Write-Verbose " Body for request: $Body" } else { Write-Verbose " Body for request: $($Body.SubString(0,40))...$($Body.SubString($Body.Length-40))" } $tmp = Invoke-RestMethod -Method $Method -Uri $RawUri -UserAgent $script:UserAgent -Headers $hdr -Body $Body -ContentType "application/json" -StatusCodeVariable code -SkipHttpErrorCheck } else { $tmp = Invoke-RestMethod -Method $Method -Uri $RawUri -UserAgent $script:UserAgent -Headers $hdr -StatusCodeVariable code -SkipHttpErrorCheck } switch ($code) { 200 { $tmp } # normal result. 204 {} # expected sometimes: no content. 302 { throw "Access denied!" } # 302 redirect to login page; should look into that and make the server return a better error, but it's OK for now. 404 { Write-Error "Item not found" } default { Write-Verbose $($tmp ?? "no acresult"); throw "Unkown/unexpected status code returned from remote API: $code" } } } # actual module functions start here... function Get-AccountSecurity { [CmdletBinding()] [OutputType([NabuAccountSecurity])] param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string] $Name ) <# .SYNOPSIS Gets the current security (login) related info for a given account. .PARAMETER Name The user (login) name to query. .OUTPUTS NabuAccountSecurity #> process { Call-Napi -Path "account/{username}/security" -Uri @{ "username" = $Name } | ForEach-Object { New-Object NabuAccountSecurity -ArgumentList $_, $Name } } } function Get-Account { [CmdletBinding()] [OutputType([NabuAccountInfo])] param ( [Parameter()] [switch] $Full ) <# .SYNOPSIS Gets the list of user accounts on the connected server. Requires admin or moderator permission. When the "Full" option is omitted, only the names are populated to speed up access. .PARAMETER Full When provided, all properties of the returned user items are filled. .OUTPUTS NabuAccountInfo #> Call-Napi -Path "account" -Query @{ "full" = $Full.IsPresent } | ForEach-Object { New-Object NabuAccountInfo -ArgumentList $_ } } function Get-UpdateImages { [CmdletBinding()] [OutputType([NabuUpdateImages])] param( ) <# .SYNOPSIS Retreives the current configured update image details. .DESCRIPTION The NabuServer supports updates for the modem firmware and config programs. One of each can be active at any time. .OUTPUTS NabuUpdateImages #> Call-Napi -Path "updates" | ForEach-Object { New-Object NabuUpdateImages -ArgumentList $_ } } function Set-FirmwareImage { [CmdletBinding(DefaultParameterSetName="path")] param( [Parameter(ParameterSetName="path")] [string]$Path, [Parameter(ParameterSetName="raw")] [byte[]]$Raw ) <# .SYNOPSIS Updates the server's firmware boot image. .DESCRIPTION The NabuServer supports updates for the modem firmware and config programs. One of each can be active at any time. This call will set the image for the firmware, based on the provided package file. .OUTPUTS NabuUpdateImages #> [string]$content = "" if ($PSCmdlet.ParameterSetName -eq "path") { $content = [System.Convert]::ToBase64String((Get-Content -AsByteStream -Path $Path)) } else { $content = [System.Convert]::ToBase64String($Raw) } [int]$assetId = Call-Napi -Path "asset/deploy" -Method "POST" -Body $content Call-Napi -Path "updates/set" -Method "PUT" -Query @{ newFirmwareAsset = $assetId } | ForEach-Object { New-Object NabuUpdateImages -ArgumentList $_ } } function Set-ConfigImage { [CmdletBinding(DefaultParameterSetName="path")] [OutputType([NabuUpdateImages])] param( [Parameter(ParameterSetName="path")] [string]$Path, [Parameter(ParameterSetName="raw")] [byte[]]$Raw ) <# .SYNOPSIS Updates the server's config program image. .DESCRIPTION The NabuServer supports updates for the modem firmware and config programs. One of each can be active at any time. This call will set the image for the config program, based on the provided package file. .OUTPUTS NabuUpdateImages #> [string]$content = "" if ($PSCmdlet.ParameterSetName -eq "path") { $content = [System.Convert]::ToBase64String((Get-Content -AsByteStream -Path $Path)) } else { $content = [System.Convert]::ToBase64String($Raw) } [int]$assetId = Call-Napi -Path "asset/deploy" -Method "POST" -Body $content Call-Napi -Path "updates/set" -Method "PUT" -Query @{ newConfigAsset = $assetId } | ForEach-Object { New-Object NabuUpdateImages -ArgumentList $_ } } function Clear-FirmwareImage { [CmdletBinding()] [OutputType([NabuUpdateImages])] param( ) <# .SYNOPSIS Clears the server's firmware boot image. .DESCRIPTION The NabuServer supports updates for the modem firmware and config programs. One of each can be active at any time. This call will set the image for the firmware, based on the provided package file. .OUTPUTS NabuUpdateImages #> Call-Napi -Path "updates/set" -Method "PUT" -Query @{ newFirmwareAsset = 0 } | ForEach-Object { New-Object NabuUpdateImages -ArgumentList $_ } } function Clear-ConfigImage { [CmdletBinding()] [OutputType([NabuUpdateImages])] param( ) <# .SYNOPSIS Clears the server's config program image. .DESCRIPTION The NabuServer supports updates for the modem firmware and config programs. One of each can be active at any time. This call will set the image for the config program, based on the provided package file. .OUTPUTS NabuUpdateImages #> Call-Napi -Path "updates/set" -Method "PUT" -Query @{ newConfigAsset = 0 } | ForEach-Object { New-Object NabuUpdateImages -ArgumentList $_ } } function Build-ConfigAsset { [CmdletBinding()] param( [Parameter(Mandatory=$true)][ValidatePattern("^[a-zA-Z0-9_\-\.][a-zA-Z0-9_\-\.\s]{1,30}[a-zA-Z0-9_\-\.]$")][string]$Title, [Parameter(Mandatory=$true)][string]$Author, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)][string]$SourceImage, [Parameter(Mandatory=$true)][string]$OutputPath ) <# .SYNOPSIS Creates a NabuNet "asset" - i.e. a ZIP file with a manifest definition for uploading as a binary "item". .DESCRIPTION Binaries in NabuNet are made up of a set of actual files, depending on use. They share a common set of base properties in a definitin file, called a "manifest"; this cmdlet will create a proper asset for deployment. Note, that there are helper functions around that simplify well known asset creation with proper parameters! .OUTPUTS #> [string]$tmp = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid().ToString("n")) Write-Verbose "Temp folder: $tmp" $file = $null New-Item -Path $tmp -ItemType Directory -ErrorAction Stop | Out-Null try { $img = Join-Path -Path $tmp -ChildPath "nabuboot.img" Copy-Item -Path $SourceImage -Destination $img Write-Verbose " Image: $img" $buf = new-object byte[] 33 $file = [System.IO.File]::OpenRead($img) $ofs = $file.ReadByte(); $ofs = $ofs + $file.ReadByte() * 256 $ofs -= 0x140D $file.Seek($ofs, "Begin") | Out-Null $len = $file.Read($buf, 0, 32) while ($len -gt 0 -and $buf[$len] -eq 0) { $len-- } if ($buf[$len] -ne 0) { $len++ } Write-Verbose "Read $len bytes from $ofs" $str = [System.Text.Encoding]::ASCII.GetString($buf, 0, $len).Trim() $file.Dispose() Write-Verbose "Read version: $str" Build-Asset -Title $Title -Version $str -Author $Author -Path $img -TempFolder $tmp -Type Config -OutputPath $OutputPath } finally { Remove-Item -Path $tmp -Recurse -Force if ($file) { $file.Dispose(); } } } function Build-FirmwareAsset { [CmdletBinding()] param( [Parameter(Mandatory=$true)][ValidatePattern("^[a-zA-Z0-9_\-\.][a-zA-Z0-9_\-\.\s]{1,30}[a-zA-Z0-9_\-\.]$")][string]$Title, [Parameter(Mandatory=$true)][ValidatePattern("^\d{1,3}\.\d{1,3}(\.\d{1,3})?(\-[a-zA-Z]{1,10})?$")] [string]$Version, [Parameter(Mandatory=$true)][string]$Author, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)][string]$SourceImage, [Parameter(Mandatory=$true)][string]$OutputPath ) <# .SYNOPSIS Creates a NabuNet "asset" - i.e. a ZIP file with a manifest definition for uploading as a binary "item". .DESCRIPTION Binaries in NabuNet are made up of a set of actual files, depending on use. They share a common set of base properties in a definitin file, called a "manifest"; this cmdlet will create a proper asset for deployment. Note, that there are helper functions around that simplify well known asset creation with proper parameters! .OUTPUTS #> [string]$tmp = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid().ToString("n")) Write-Verbose "Temp folder: $tmp" New-Item -Path $tmp -ItemType Directory -ErrorAction Stop | Out-Null try { $img = Join-Path -Path $tmp -ChildPath "nabufirm.img" Copy-Item -Path $SourceImage -Destination $img Write-Verbose " Image: $img" Build-Asset -Title $Title -Version $Version -Author $Author -Path $img -TempFolder $tmp -BigBlob -Type Firmware -OutputPath $OutputPath } finally { Remove-Item -Path $tmp -Recurse -Force } } function Build-Asset { [CmdletBinding()] param( [Parameter(Mandatory=$true)][ValidatePattern("^[a-zA-Z0-9_\-\.][a-zA-Z0-9_\-\.\s]{1,30}[a-zA-Z0-9_\-\.]$")][string]$Title, [Parameter(Mandatory=$true)][ValidatePattern("^\d{1,3}\.\d{1,3}(\.\d{1,3})?(\-[a-zA-Z]{1,10})?$")] [string]$Version, [Parameter(Mandatory=$true)][string]$Author, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)][ValidateCount(1,255)] [string[]]$Path, [Parameter(Mandatory=$true)][string]$OutputPath, [ValidateSet("ResourceOnly","Kernel","Program","Firmware","Config")][string]$Type = "ResourceOnly", [string]$KernelType = "NabuNet", [switch]$BigBlob, [string]$TempFolder = $null ) <# .SYNOPSIS Creates a NabuNet "asset" - i.e. a ZIP file with a manifest definition for uploading as a binary "item". .DESCRIPTION Binaries in NabuNet are made up of a set of actual files, depending on use. They share a common set of base properties in a definitin file, called a "manifest"; this cmdlet will create a proper asset for deployment. Note, that there are helper functions around that simplify well known asset creation with proper parameters! .OUTPUTS #> Write-Verbose "Build asset.." [string]$tmp = $null if ($TempFolder -eq $null) { $tmp = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid().ToString("n")) New-Item -Path $tmp -ItemType Directory | Out-Null $TempFolder = $tmp } try { [string]$mani = Join-Path -Path $TempFolder -ChildPath "manifest.json" $list = @(Get-Item -Path $Path -ErrorAction Stop) Write-Verbose "Found $($list.Length) files." [bool]$hadError = $false [int]$limit = 0x10000 if ($BigBlob.IsPresent) { $limit = 4mb } $itemErrors = @($list | Where-Object -Property Length -gt $limit) $itemErrors | ForEach-Object { Write-Error "Item $_ is too large! Maximum of $limit!" } $hadError = $hadError -or [bool]$itemErrors Write-Verbose "Found $($itemErrors.Length) files with size limit escalation." $itemErrors = @($list | Where-Object -Property Name -NotMatch "^[a-zA-Z0-9_\-]{1,8}(\.[a-zA-Z0-9_\-]{0,3})?$") $itemErrors | ForEach-Object { Write-Error "Item $_ has an invalid filename. Must meet 8.3 specs and only contain alphanumeric characters." } $hadError = $hadError -or [bool]$itemErrors Write-Verbose "Found $($itemErrors.Length) files with file name problems." $itemErrors = @($list | Where-Object { $_ -isnot [System.IO.FileInfo]} ) $itemErrors | ForEach-Object { Write-Error "Item $_ is not a file!" } $hadError = $hadError -or [bool]$itemErrors Write-Verbose "Found $($itemErrors.Length) items which aren't a file!" if ($hadError) { throw "Cannot continue with file errors!" } @{title = $Title; version = $Version; author = $Author; type = $Type.ToLower(); kerneltype = $KernelType; assets = @($list | Select-Object -ExpandProperty Name)} | ConvertTo-Json | Out-File -FilePath $mani $list = @($list | Select-Object -ExpandProperty FullName) $list += $mani Write-Verbose "Attaching $($list.Length) files." Compress-Archive -DestinationPath $OutputPath -Path $list -CompressionLevel Optimal -Force } finally { if ($tmp) { Remove-Item -Path $tmp -Recurse -Force } } } function Get-ServerAnnouncement { [CmdletBinding()] [OutputType([NabuArticleBase])] param ( [switch]$Raw ) <# .SYNOPSIS Retreives the current server announcement message (maintenance notice) .DESCRIPTION The Nabu Server has a single, central special article for server maintenance info. If set, it will be shown prominently on the landing page and some other key pages as a headline with a link to the full article. .OUTPUTS NabuArticleBase .PARAMETER Raw If provided, the returned article info from the server will be the raw (markdown) text, if not a plain text rendering is provided. #> Call-Napi -Path "announcement" -Query @{ raw = $Raw.IsPresent } | ForEach-Object { New-Object NabuArticleBase -ArgumentList $_ } } function Clear-ServerAnnouncement { <# .SYNOPSIS Clears (removes) a server announcement message (maintenance notice) .DESCRIPTION The Nabu Server has a single, central special article for server maintenance info. If set, it will be shown prominently on the landing page and some other key pages as a headline with a link to the full article. .OUTPUTS nothing #> [CmdletBinding(PositionalBinding = $false, ConfirmImpact = "Medium", SupportsShouldProcess = $true)] param () if ($PSCmdlet.ShouldProcess($CurrentContext.Host.Name, "Remove announcement message on server")) { Write-Verbose "Removing server announcement message." Call-Napi -Path "announcement" -Method "DELETE" | Out-Null } } function Set-ServerAnnouncement { <# .SYNOPSIS Sets (updates or creates) a server announcement message (maintenance notice) .DESCRIPTION The Nabu Server has a single, central special article for server maintenance info. If set, it will be shown prominently on the landing page and some other key pages as a headline with a link to the full article. .OUTPUTS NabuArticleBase .PARAMETER Title The title for the article. .PARAMETER Article The body (full text, markdown!) for the article. .PARAMETER ReferenceDate If provided, indicates a date (and time) for the operation in question. The server will include that info on the details page, complete with absolute and relative server time info. .PARAMETER Raw If provided, the returned article info from the server will be the raw (markdown) text, if not a plain text rendering is provided. #> [CmdletBinding(PositionalBinding = $false, ConfirmImpact = "Medium", SupportsShouldProcess = $true)] [OutputType([NabuArticleBase])] param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string]$Title, [Parameter(Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string]$Article, [Parameter(ValueFromPipelineByPropertyName = $true)] [Nullable[datetime]]$ReferenceDate = $null, [switch]$Raw ) process { if ($PSCmdlet.ShouldProcess($CurrentContext.Host.Name, "Update announcement message on server, $Title")) { Write-Verbose "Setting server announcement message to '$Title', $($Article.Length) characters article..." Call-Napi -Path "announcement" -Method "POST" -Query @{ raw = $Raw.IsPresent } -Body @{ title = $Title; article = $Article; referenceDate = $ReferenceDate } | ForEach-Object { New-Object NabuArticleBase -ArgumentList $_ } } } } function Register-Host { <# .SYNOPSIS Creates a new server registration for the current user. .DESCRIPTION The server configuration is stored in the user home folder, subfolder ".nabunet" as a configuration file with the server name as a root name, psd1 as an extension. If a token is provided, it will be used for authentication, if not, the token needs to be provided by calling the Update-NabuNetHost cmdlet. .PARAMETER Name The local name to use for the server. Defaults to the host name if not specified. .PARAMETER Host The host name of the remote server. .PARAMETER Token The security token (API Token) as for the user. .PARAMETER Port The TCP port number - defaults to 443 for HTTPS. .PARAMETER Path The path of the NAPI interface, defaults to the "/napi" folder. #> [CmdletBinding(PositionalBinding = $false)] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$Host, [Parameter(Position = 1)] [string]$Name = $null, [string]$Token = $null, [ValidateRange(1, 65535)] [int]$Port = 443, [ValidatePattern("^(/[a-zA-Z0-9\-\.\_]+)+$", ErrorMessage = "Not a valid path for NAPI. Must start with a / and only contain letters, numbers, dashes and dots.")] [string]$Path = "/napi" ) if ($null -eq $Name) { $Name = $Host } $h = Load-RegisteredHost -Name $Name if ($null -ne $h) { throw "The name $Name is already registered! Remove it first or use Update-NabuNetHost" } $h = New-Object NabuHost -ArgumentList $Name $h.RemoteBaseUri = "https://$($Host):$Port$Path" $h.Registered = [DateTime]::UtcNow $h.LastContacted = [DateTime]::MinValue $h.Token = $Token $h.RemoteName = $null $h.RemoteTagline = $null #-ArgumentList @{Host = $Host; Port = $port; Path = $Path; Registered = [DateTime]::UtcNow; LastContacted = [DateTime]::MinValue; RemoteName = $null; RemoteTagline = $null } Save-RegisteredHost -Name $Name -Data $h } function Connect-Host { <# .SYNOPSIS Connect the current powershell sessino to a server. .DESCRIPTION You can either save a connection with a name (see Register-NabuNetHost) and use that name for easy connectivity, or specify all required parameters here for a dynamic, not saved connection. .EXAMPLE Connect-NabuNetHost -Name testserver Connects to the previously registered server (and token!) called "testserver". .LINK Connect-NabuNetHost .PARAMETER Name The name of a registered server. .PARAMETER Host The host name of the remote server. .PARAMETER Token The security token (API Token) as for the user. .PARAMETER Port The TCP port number - defaults to 443 for HTTPS. .PARAMETER Path The path of the NAPI interface, defaults to the "/napi" folder. #> [CmdletBinding(PositionalBinding = $false)] [OutputType([NabuHostInfo])] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Temporary")] [string]$Host, [Parameter(Position = 1, ParameterSetName = "Temporary")] [string]$Token = $null, [Parameter(ParameterSetName = "Temporary")] [ValidateRange(1, 65535)] [int]$Port = 443, [Parameter(ParameterSetName = "Temporary")] [ValidatePattern("^(/[a-zA-Z0-9\-\.\_]+)+$", ErrorMessage = "Not a valid path for NAPI. Must start with a / and only contain letters, numbers, dashes and dots.")] [string]$Path = "/napi", [Parameter(ParameterSetName = "Named", Mandatory = $true)] [string]$Name ) if ($PSCmdlet.ParameterSetName -eq "Named") { # call by registered name... $hi = Load-RegisteredHost -Name $Name } else { # call by temp parameters... $hi = New-Object NabuHost -ArgumentList "<none>" $hi.RemoteBaseUri = "https://$($Host):$Port$Path" $hi.Registered = [DateTime]::UtcNow $hi.LastContacted = [DateTime]::MinValue $hi.Token = $Token $hi.RemoteName = $null $hi.RemoteTagline = $null } Write-Verbose "Connecting to $($hi.RemoteBaseUri)..." $result = Call-Napi -Connection $hi -Path "info" if ($result) { $hi.LastContacted = [DateTime]::UtcNow $hi.RemoteName = $result.name $hi.RemoteTagline = $result.tagLine $script:CurrentContext.Host = $hi $script:CurrentContext.Token = $null $script:CurrentContext.TokenExpires = $null if ($PSCmdlet.ParameterSetName -eq "Named") { # update saved info with most recent data... Save-RegisteredHost -Name $Name -Data $hi } New-Object NabuHostInfo -ArgumentList $CurrentContext.Host, $CurrentContext.Name } else { throw "Remote system didn't return a valid info object!" } } function Get-Host { <# .SYNOPSIS Lists the current connected Nabu server or a list of all registered servers for the current user. .PARAMETER List If specified, the registered servers will be listed. If not, the currently connected one will be shown. .OUTPUTS NabuHostInfo #> [CmdletBinding(PositionalBinding = $false)] [OutputType([NabuHostInfo])] param ( [switch]$List ) if ($List.IsPresent) { #Get-AllRegisteredHost | ForEach-Object { Import-PowerShellDataFile -Path $_.FullName } Get-AllRegisteredHost | ForEach-Object { New-Object NabuHostInfo -ArgumentList (Import-Clixml -Path $_.FullName), $_.Name.Replace(".xml", "") } #| Where-Object { $_ -is [NabuHost] } } else { if ($CurrentContext.Host) { New-Object NabuHostInfo -ArgumentList $CurrentContext.Host, $CurrentContext.Name } } } function Get-MailTemplate { <# .SYNOPSIS Retreives an e-mail template from the NabuNet server. .DESCRIPTION Asks the server for the subject/body of the e-mail template. Needs site admin privileges. .PARAMETER Id The ID (key) of the mail template. .OUTPUTS NabuTemplateInfo #> [CmdletBinding()] [OutputType([NabuTemplateInfo])] param ( [Parameter(Mandatory = $true)] [ValidatePattern("^[a-zA-Z0-9-]+$", ErrorMessage = "Only digits and letters allowed!")] [string]$Id ) Call-Napi -Path "template/{id}" -Uri @{ "id" = $id } | ForEach-Object { New-Object NabuTemplateInfo -ArgumentList $_, $id } } function Set-MailTemplate { <# .SYNOPSIS Updated an e-mail template on the NabuNet server. .DESCRIPTION Sends the new subject/body of the e-mail template to the server. Needs site admin privileges. .PARAMETER Id The ID (key) of the mail template. .PARAMETER Subject The subject line for the e-mail. Can include handlebars.net placeholders according to the template definition. .PARAMETER Body The body of the e-mail. Can include handlebars.net placeholders and should be HTML formatted. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")] param ( [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [ValidatePattern("^[a-zA-Z0-9-]+$", ErrorMessage = "Only digits and letters allowed!")] [string]$Id, [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [ValidateLength(1, 1024)] [ValidatePattern("^[^\n\r]+$", ErrorMessage = "No newlines allowed!")] [string]$Subject, [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [string]$Body ) process { if ($PSCmdlet.ShouldProcess("$Id", "Updating mail template, new subject=$Subject, body=$($body.Length) characters.")) { Call-Napi -Path "template/{id}" -Uri @{"id" = $Id} -Method "POST" -Body @{ Subject = $Subject; Body = $Body } } } } # approveaccount/{userName} function Approve-Account { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")] param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateLength(1, 32)] [ValidatePattern("^[a-zA-Z0-9-]+$", ErrorMessage = "Only digits and letters allowed!")] [string] $Name ) <# .SYNOPSIS Approves an account that was held back because of manual approval requiremenst. .PARAMETER Name The user login name to approve. .DESCRIPTION If the server requires manual approval of new accounts, the "validate mail address" e-mail is not sent before this step is completed! #> process { if ( $PSCmdlet.ShouldProcess($Name, "Confirm user account")) { Call-Napi -Path "account/{username}/approve" -Uri @{"username" = $Name} -Method "PUT" } } } # diag/testmail function Test-Mailing { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low")] param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateLength(1, 128)] [string] $Receiver, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string] $Subject, [Parameter(ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $Info = "No further information provided." ) <# .SYNOPSIS Sends a test message to a provided e-mail address and (as BCC) to the calling user's contact address. .PARAMETER Receiver The e-mail address to send the test message to. .PARAMETER Subject A few words to add to the subject line for reference. .PARAMETER Info A text (can be longer) that will be embedded as "pre" formatted text in the mail body. .DESCRIPTION The server will use a standard diagnostic message template to adorn the provided values; this helps preventing abuse of this diag feature, besides only being allowed to site admins. #> process { if ($PSCmdlet.ShouldProcess($Receiver, "Send test mail message")) { Call-Napi -Path "diag/testmail" -Query @{"targetMail" = $Receiver; "subject" = $Subject } -Body $Info -Method "PUT" } } } |