Portal.psm1
# # Script module for module 'Portal' # #Requires -Version 7.1 #Requires -PSEdition Core using namespace System.Management.Automation #region Enum enum Scopes { # Scopes in which a connection can be opened Undefined = 1 Console = 2 External = 3 } #endregion Enums #region Classes class Item { # Name [ValidateLength(1, 50)] [ValidatePattern("^([a-zA-Z0-9_\-]+)$")] [string] $Name # Description [string] $Description [hashtable] Splat() { $Hashtable = [ordered] @{} foreach ($p in $this.PSObject.Properties) { $this.PSObject.Properties | ForEach-Object -Process { $Hashtable[$p.Name] = $p.Value } } return $Hashtable } } class Client : Item { # Executable [string] $Executable # Command template [string] $TokenizedArgs # Default port [UInt16] $DefaultPort # Default connection scope [Scopes] $DefaultScope # Does this client require a user [bool] $RequiresUser # Tokens static [string] $HostToken = "<host>" static [string] $PortToken = "<port>" static [string] $UserToken = "<user>" Client( [string] $Name, [string] $Executable, [string] $TokenizedArgs, [UInt16] $DefaultPort, [Scopes] $DefaultScope, [string] $Description ) { $this.Name = $Name $this.Executable = $Executable [Client]::ValidateTokenizedArgs($TokenizedArgs) $this.TokenizedArgs = $TokenizedArgs $this.DefaultPort = $DefaultPort $this.DefaultScope = $DefaultScope $this.RequiresUser = [Client]::UserTokenExists($TokenizedArgs) $this.Description = $Description } static [void] ValidateTokenizedArgs( [string] $TokenizedArgs ) { @( [Client]::HostToken, [Client]::PortToken ) | ForEach-Object -Process { if ($TokenizedArgs -notmatch $_) { throw "The argument line does not contain the following token: {0}." -f $_ } } } hidden static [bool] UserTokenExists([string] $TokenizedArgs) { return $TokenizedArgs -match [Client]::UserToken } [string] GenerateArgs( [string] $Hostname, [UInt16] $Port ) { return $this.TokenizedArgs.Replace( [Client]::HostToken, $Hostname ).Replace( [Client]::PortToken, $Port ) } [string] GenerateArgs( [string] $Hostname, [UInt16] $Port, [string] $User ) { return $this.GenerateArgs($Hostname, $Port).Replace( [Client]::UserToken, $User ) } [string] ToString() { return "{0}, Description: `"{1}`", Scope: {2}, Command: `"{3} {4}`"" -f ( $this.Name, $this.Description, $this.DefaultScope, $this.Executable, $this.TokenizedArgs.Replace( "<port>", "<port:{0}>" -f $this.DefaultPort ) ) } } class Connection : Item { # Hostname [ValidatePattern("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")] [string] $Hostname # Port [UInt16] $Port # Default client [string] $DefaultClient # Default user [string] $DefaultUser Connection( [String] $Name, [String] $Hostname, [UInt16] $Port, [string] $DefaultClient, [string] $DefaultUser, [string] $Description ) { $this.Name = $Name $this.Hostname = $Hostname.ToLower() $this.Port = $Port $this.DefaultClient = $DefaultClient $this.DefaultUser = $DefaultUser $this.Description = $Description } [bool] IsDefaultPort() { return $this.Port -eq 0 } [string] ToString() { return "{0}, Description `"{1}`", Default client {2}, Target {3}:{4}" -f ( $this.Name, $this.Description, $this.DefaultClient, $this.Hostname, $( if ($this.IsDefaultPort()) { "default" } else { $this.Port.ToString() } ) ) } } class Inventory { # Title for the inventory file [string] $Title = "Portal inventory" # description for the inventory file [string] $Description = "Portal inventory file where the connections and clients are stored" # Version of the inventory file [string] $Version = "0.1.0" # Path to the inventory file [string] $Path = [Inventory]::GetPath() # Collection of Clients [Client[]] $Clients # Collection of Connections [Connection[]] $Connections # Encoding for inventory file static [string] $Encoding = "utf-8" # Name of the environement variable to use a custom path to the inventory file static [string] $EnvVariable = "PORTAL_INVENTORY" static [string] GetPath() { foreach ($Target in @("Process", "User", "Machine")) { $Value = [System.Environment]::GetEnvironmentVariable( [Inventory]::EnvVariable, [System.EnvironmentVariableTarget]::"$Target" ) if ($Value) { return $Value } } return Join-Path -Path $env:APPDATA -ChildPath "Portal/inventory.json" } [void] ReadFile() { # Get content from the file $GetContentParams = @{ Path = $this.Path Raw = $true Encoding = [Inventory]::Encoding ErrorAction = "Stop" } try { $Items = Get-Content @GetContentParams | ConvertFrom-Json -AsHashtable } catch { throw "Cannot open inventory: {0}" -f $_.Exception.Message } # Check version of the inventory if ($Items.Version -ne $this.Version) { throw ( "Version of the inventory file is not supported.", "Current version: `"{0}`", Expected version: `"{1}`"" -f ( $Items.Version, $this.Version ) ) } # Re-initialize Clients array $this.Clients = @() # Add every Client to inventory object foreach ($c in $Items.Clients) { $this.Clients += New-Object -TypeName Client -ArgumentList @( $c.Name, $c.Executable, $c.TokenizedArgs, $c.DefaultPort, $c.DefaultScope, $c.Description ) } # Check if Client name duplicates exist if ($this.ClientNameDuplicatesExist()) { Write-Warning -Message ("Fix the inventory by renaming the duplicated client names in the inventory file: {0}" -f ( [Inventory]::GetPath() ) ) } # Re-initialize Connections array $this.Connections = @() # Add every Connection to inventory object foreach ($c in $Items.Connections) { $this.Connections += New-Object -TypeName Connection -ArgumentList @( $c.Name, $c.Hostname, $c.Port, $c.DefaultClient, $c.DefaultUser, $c.Description ) } # Check if Connection name duplicates exist if ($this.ConnectionNameDuplicatesExist()) { Write-Warning -Message ( "Fix the inventory by renaming the duplicated connection names in the inventory file: {0}" -f ( [Inventory]::GetPath() ) ) } } [void] SaveFile() { $Items = [ordered] @{ Title = $this.Title Description = $this.Description Version = $this.Version Clients = @() Connections = @() } foreach ($c in $this.Clients) { $Items.Clients += $c.Splat() } foreach ($c in $this.Connections) { $Connection = $c.Splat() $Items.Connections += $Connection } $Json = ConvertTo-Json -InputObject $Items -Depth 3 if (Test-Path -Path $this.Path -PathType Leaf) { $BackupPath = "{0}.backup" -f $this.Path Copy-Item -Path $this.Path -Destination $BackupPath -Force } else { New-Item -Path $this.Path -ItemType File -Force | Out-Null } Set-Content -Path $this.Path -Value $Json -Encoding ([Inventory]::Encoding) -Force } hidden [bool] ClientNameDuplicatesExist() { $Duplicates = $this.Clients | Group-Object -Property Name | Where-Object -Property Count -GT 1 if ($Duplicates) { $Duplicates | ForEach-Object -Process { Write-Warning -Message ("It exists more than one client named `"{0}`"." -f $_.Name) } return $true } return $false } hidden [bool] ConnectionNameDuplicatesExist() { $Duplicates = $this.Connections | Group-Object -Property Name | Where-Object -Property Count -GT 1 if ($Duplicates) { $Duplicates | ForEach-Object -Process { Write-Warning -Message ("It exists more than one connection named `"{0}`"." -f $_.Name) } return $true } return $false } [Client] GetClient([string] $Name) { return $this.Clients | Where-Object -Property Name -EQ $Name } [Connection] GetConnection([string] $Name) { return $this.Connections | Where-Object -Property Name -EQ $Name } [bool] ClientExists([string] $Name) { return $this.GetClient($Name).Count -gt 0 } [bool] ConnectionExists([string] $Name) { return $this.GetConnection($Name).Count -gt 0 } [void] AddClient([Client] $Client) { if ($this.ClientExists($Client.Name)) { throw "Cannot add Client `"{0}`" as it already exists." -f $Client.Name } $this.Clients += $Client } [void] AddConnection([Connection] $Connection) { if ($this.ConnectionExists($Connection.Name)) { throw "Cannot add Connection `"{0}`" as it already exists." -f $Connection.Name } $this.Connections += $Connection } [void] RemoveClient([string] $Name) { $this.Clients = $this.Clients | Where-Object -Property Name -NE $Name } [void] RemoveConnection([string] $Name) { $this.Connections = $this.Connections | Where-Object -Property Name -NE $Name } [void] RenameClient([string] $Name, [string] $NewName) { if ($Name -eq $NewName) { throw "The two names are similar." } elseif (-not $this.ClientExists($Name)) { throw "No Client `"{0}`" to rename." -f $Name } elseif ($this.ClientExists($NewName)) { throw "Cannot rename Client `"{0}`" to `"{1}`" as this name is already used." -f $Name, $NewName } for ($i = 0; $i -lt $this.Clients.count; $i++) { if ($this.Clients[$i].Name -eq $Name) { $this.Clients[$i].Name = $NewName } } for ($i = 0; $i -lt $this.Connections.count; $i++) { if ($this.Connections[$i].DefaultClient -eq $Name) { $this.Connections[$i].DefaultClient = $NewName } } } [void] RenameConnection([string] $Name, [string] $NewName) { if ($Name -eq $NewName) { throw "The two names are similar." } elseif (-not $this.ConnectionExists($Name)) { throw "No Connection `"{0}`" to rename." -f $Name } elseif ($this.ConnectionExists($NewName)) { throw "Cannot rename Connection `"{0}`" to `"{1}`" as this name is already used." -f $Name, $NewName } for ($i = 0; $i -lt $this.Connections.count; $i++) { if ($this.Connections[$i].Name -eq $Name) { $this.Connections[$i].Name = $NewName } } } } class ValidateClientName : ValidateArgumentsAttribute { [void] Validate( [System.Object] $Argument, [System.Management.Automation.EngineIntrinsics] $EngineIntrinsics ) { $Inventory = Import-Inventory if (-not $Inventory.ClientExists($Argument)) { Throw "Client `"{0}`" does not exist." -f $Argument } } } class ValidateConnectionName : ValidateArgumentsAttribute { [void] Validate( [System.Object] $Argument, [System.Management.Automation.EngineIntrinsics] $EngineIntrinsics ) { $Inventory = Import-Inventory if (-not $Inventory.ConnectionExists($Argument)) { Throw "Connection `"{0}`" does not exist." -f $Argument } } } class ValidateSetClientName : IValidateSetValuesGenerator { [string[]] GetValidValues() { try { $Inventory = Import-Inventory return $Inventory.Clients | ForEach-Object -Process { $_.Name } } catch { Write-Warning -Message $_.Exception.Message } return $null } } class ValidateSetConnectionName : IValidateSetValuesGenerator { [string[]] GetValidValues() { try { $Inventory = Import-Inventory return $Inventory.Connections | ForEach-Object -Process { $_.Name } } catch { Write-Warning -Message $_.Exception.Message } return $null } } #endregion Classes #region Private functions function Import-Inventory { <# .SYNOPSIS Import inventory. .DESCRIPTION Creates inventory object, reads inventory file and returns the object. .INPUTS None. You cannot pipe objects to Import-Inventory. .OUTPUTS Inventory. Import-Inventory returns the inventory object. .EXAMPLE PS> Import-Inventory (Inventory) #> [OutputType([Inventory])] param () process { $Inventory = New-Object -TypeName Inventory $Inventory.ReadFile() $Inventory } } function New-DefaultClients { <# .SYNOPSIS Creates default clients. .DESCRIPTION Creates and returns Client objects with popular programs. .INPUTS None. You cannot pipe objects to New-DefaultClients. .OUTPUTS Client[]. New-DefaultClients returns an array of Client objects. .EXAMPLE PS> New-DefaultClients (Client[]) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope = "Function", Target = "*")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Scope = "Function", Target = "*")] [OutputType([Client[]])] param () process { $DefaultClient = @() # OpenSSH (Microsoft Windows feature) $DefaultClient += New-Object -TypeName Client -ArgumentList @( "OpenSSH", "C:\Windows\System32\OpenSSH\ssh.exe", "-l <user> -p <port> <host>", 22, [Scopes]::Console, "OpenSSH (Microsoft Windows feature)" ) # PuTTY using SSH protocol $DefaultClient += New-Object -TypeName Client -ArgumentList @( "PuTTY_SSH", "putty.exe", "-ssh -P <port> <user>@<host>", 22, [Scopes]::External, "PuTTY using SSH protocol" ) # Microsoft Remote Desktop $DefaultClient += New-Object -TypeName Client -ArgumentList @( "RD", "C:\Windows\System32\mstsc.exe", "/v:<host>:<port> /fullscreen", 3389, [Scopes]::External, "Microsoft Remote Desktop" ) $DefaultClient } } #endregion Private functions #region Public functions function Add-Portal { <# .SYNOPSIS Adds Portal connection. .DESCRIPTION Adds connection entry to the Portal inventory file. .PARAMETER Name Name of the connection. .PARAMETER Hostname Name of the remote host. .PARAMETER Port Port to connect to on the remote host. If not set, it will use the default port of the client. .PARAMETER DefaultClient Name of the default client. .PARAMETER DefaultUser Default client to use to connect to the remote host. .PARAMETER Description Short description for the connection. .INPUTS None. You cannot pipe objects to Add-Portal. .OUTPUTS System.Void. None. .EXAMPLE PS> Add-Portal -Name myconn -Hostname myhost -DefaultClient SSH .EXAMPLE PS> Add-Portal -Name myrdpconn -Hostname myhost -DefaultClient RDP -Description "My RDP connection" .EXAMPLE PS> Add-Portal -Name mysshconn -Hostname myhost -Port 2222 -DefaultClient SSH -DefaultUser myuser -Description "My SSH connection" #> [OutputType([string])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Mandatory = $true, HelpMessage = "Name of the connection." )] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter( Mandatory = $true, HelpMessage = "Name of the remote host." )] [ValidateNotNullOrEmpty()] [string] $Hostname, [Parameter( HelpMessage = "Port to connect to on the remote host." )] [UInt16] $Port, [Parameter( Mandatory = $true, HelpMessage = "Default client to use to connect to the remote host." )] [ValidateSet([ValidateSetClientName])] [ValidateClientName()] [string] $DefaultClient, [Parameter( HelpMessage = "Default user to use to connect to the remote host." )] [string] $DefaultUser, [Parameter( HelpMessage = "Short description of the connection." )] [string] $Description ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Cannot open inventory: {0}" -f $_.Exception.Message ) } try { $Connection = New-Object -TypeName Connection -ArgumentList @( $Name, $Hostname, $Port, $DefaultClient, $DefaultUser, $Description )# } catch { Write-Error -Message ( "Cannot create new connection: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, "Add Connection {0}" -f $Connection.ToString() ) ) { $Inventory.AddConnection($Connection) try { $Inventory.SaveFile() Write-Verbose -Message ( "Connection `"{0}`" has been added to the inventory." -f $Name ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Add-PortalClient { <# .SYNOPSIS Adds Portal client. .DESCRIPTION Adds client entry to the Portal inventory file. .PARAMETER Name Name of the client. .PARAMETER Executable Path to the executable program that the client uses. .PARAMETER Arguments String of Arguments to pass to the executable. The string should contain the required tokens. Please read the documentation of Portal. .PARAMETER DefaultPort Network port to use if the connection has no defined port. .PARAMETER DefaultScope Default scope in which a connection will be opened. .PARAMETER Description Short description for the client. .INPUTS None. You cannot pipe objects to Add-PortalClient. .OUTPUTS System.Void. None. .EXAMPLE PS> Add-PortalClient -Name SSH -Executable "ssh.exe" -Arguments "-l <user> -p <port> <host>" -DefaultPort 22 .EXAMPLE PS> Add-PortalClient -Name MyCustomClient -Executable "client.exe" -Arguments "--hostname <host> --port <port>" -DefaultPort 666 -DefaultScope External -Description "My custom client" #> [OutputType([string])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Mandatory = $true, HelpMessage = "Name of the client." )] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter( Mandatory = $true, HelpMessage = "Path to the executable to run as client." )] [ValidateNotNullOrEmpty()] [string] $Executable, [Parameter( Mandatory = $true, HelpMessage = "Arguments as a tokenized string. Please, read the documentation to get the list of tokens." )] [ValidateNotNullOrEmpty()] [string] $Arguments, [Parameter( Mandatory = $true, HelpMessage = "Default port to connect to on the remote host." )] [ValidateNotNullOrEmpty()] [UInt16] $DefaultPort, [Parameter( HelpMessage = "Default scope in which a connection will be opened." )] [ValidateNotNullOrEmpty()] [Scopes] $DefaultScope = [Scopes]::Console, [Parameter( HelpMessage = "Short description of the client." )] [string] $Description ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Error inventory: {0}" -f $_.Exception.Message ) } try { [Client]::ValidateTokenizedArgs($Arguments) } catch { Write-Error -Message ( $_.Exception.Message ) } try { $Client = New-Object -TypeName Client -ArgumentList @( $Name, $Executable, $Arguments, $DefaultPort, $DefaultScope, $Description ) } catch { Write-Error -Message ( "Cannot create new client: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, "Add Client {0}" -f $Client.ToString() ) ) { $Inventory.AddClient($Client) try { $Inventory.SaveFile() Write-Verbose -Message ( "Client `"{0}`" has been added to the inventory." -f $Name ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Get-Portal { <# .SYNOPSIS Gets Portal connections. .DESCRIPTION Gets available connections from the Portal inventory file. connections can be filtered by their name and/or client name. .PARAMETER Name Filters connections by name. .INPUTS None. You cannot pipe objects to Get-Portal. .OUTPUTS PSCustomObject. Get-Portal returns objects with details of the available connections. .EXAMPLE PS> Get-Portal (objects) .EXAMPLE PS> Get-Portal -Name "myproject_*" -Hostname "*.mydomain" -Client "*_myproject" (filtered objects) #> [OutputType([PSCustomObject[]])] [CmdletBinding()] param ( [Parameter( HelpMessage = "Filter by connection name." )] [ValidateNotNullOrEmpty()] [string] $Name = "*", [Parameter( HelpMessage = "Filter by hostname." )] [ValidateNotNullOrEmpty()] [string] $Hostname = "*", [Parameter( HelpMessage = "Filter by client name." )] [ValidateNotNullOrEmpty()] [string] $Client = "*" ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Error import inventory: {0}" -f $_.Exception.Message ) } $Connections = @() foreach ($c in $Inventory.Connections) { $Connections += [PSCustomObject] @{ Name = $c.Name Hostname = $c.Hostname Port = if ($c.IsDefaultPort) { $Inventory.GetClient($c.DefaultClient).DefaultPort } else { $c.Port } DefaultClient = $c.DefaultClient DefaultUser = $c.DefaultUser Description = $c.Description } } $Connections | Where-Object -Property Name -Like $Name | Where-Object -Property Hostname -Like $Hostname | Where-Object -Property DefaultClient -Like $Client | Sort-Object -Property Name } } function Get-PortalClient { <# .SYNOPSIS Gets Portal clients. .DESCRIPTION Gets available clients from the Portal inventory file. Clients can be filtered by their name. .PARAMETER Name Filters clients by name. .INPUTS None. You cannot pipe objects to Get-PortalClient. .OUTPUTS PSCustomObject. Get-PortalClient returns objects with details of the available clients. .EXAMPLE PS> Get-PortalClient (objects) .EXAMPLE PS> Get-PortalClient -Name "custom_*" (filtered objects) #> [OutputType([PSCustomObject[]])] [CmdletBinding()] param ( [Parameter( HelpMessage = "Filter by client name." )] [ValidateNotNullOrEmpty()] [string] $Name = "*" ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Error import inventory: {0}" -f $_.Exception.Message ) } $Clients = @() foreach ($c in $Inventory.Clients) { $Clients += [PSCustomObject] @{ Name = $c.Name Command = "{0} {1}" -f $c.Executable, $c.TokenizedArgs DefaultPort = $c.DefaultPort DefaultScope = $c.DefaultScope Description = $c.Description } } $Clients | Where-Object -Property Name -Like $Name | Sort-Object -Property Name } } function Get-PortalInventory { <# .SYNOPSIS Gets Portal inventory information. .DESCRIPTION Gets detailed information about the Portal inventory. .INPUTS None. You cannot pipe objects to Get-PortalInventory. .OUTPUTS PSCustomObject. Get-PortalInventory returns an object with detailed information. .EXAMPLE PS> Get-PortalInventory (objects) #> [OutputType([PSCustomObject])] [CmdletBinding()] param () process { $Inventory = New-Object -TypeName Inventory $FileExists = $false if (Test-Path -Path $Inventory.Path -PathType Leaf) { $Inventory.ReadFile() $FileExists = $true } [PSCustomObject] @{ Path = $Inventory.Path EnvVariable = [Inventory]::EnvVariable FileExists = $FileExists NumberOfClients = $Inventory.Clients.Count NumberOfConnections = $Inventory.Connections.Count } } } function New-PortalInventory { <# .SYNOPSIS Creates Portal inventory file. .DESCRIPTION Creates a new inventory file where Portal saves items. .PARAMETER NoDefaultClients Does not add defaults clients to the new inventory. .PARAMETER Force Overwrites existing inventory file. .PARAMETER PassThru Indicates that the cmdlet sends items from the interactive window down the pipeline as input to other commands. .INPUTS None. You cannot pipe objects to New-PortalInventory. .OUTPUTS System.Void. None. or if PassThru is set, System.String. New-PortalInventory returns a string with the path to the created inventory. .EXAMPLE PS> New-PortalInventory .EXAMPLE PS> New-PortalInventory -NoDefaultClients .EXAMPLE PS> New-PortalInventory -Force .EXAMPLE PS> New-PortalInventory -PassThru C:\Users\MyUsername\Portal.json .EXAMPLE PS> New-PortalInventory -NoDefaultClients -Force -PassThru C:\Users\MyUsername\Portal.json #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( HelpMessage = "Do not add defaults clients." )] [switch] $NoDefaultClients, [Parameter( HelpMessage = "Overwrite existing inventory file." )] [switch] $Force, [Parameter( HelpMessage = "Indicates that the cmdlet sends items from the interactive window down the pipeline as input to other commands." )] [switch] $PassThru ) begin { $ErrorActionPreference = "Stop" } process { $Inventory = New-Object -TypeName Inventory if ((Test-Path -Path $Inventory.Path -PathType Leaf) -and -not ($Force.IsPresent)) { Write-Error -ErrorAction Stop -Exception ( [System.IO.IOException] "Inventory file already exists. Use `"-Force`" to overwrite it." ) } if ($PSCmdlet.ShouldProcess($Inventory.Path, "Create inventory file")) { if (-not $NoDefaultClients.IsPresent) { New-DefaultClients | ForEach-Object -Process { $Inventory.AddClient($_) } } try { $Inventory.SaveFile() Write-Verbose -Message ( "Inventory file has been created: {0}" -f $Inventory.Path ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } if ($PassThru.IsPresent) { Resolve-Path $Inventory.Path | Select-Object -ExpandProperty Path } } } function Open-Portal { <# .SYNOPSIS Opens Portal connection. .DESCRIPTION Opens Portal connection which is defined in the inventory. .PARAMETER Name Name of the connection. .PARAMETER Client Name of the client to use to initiate the connection. .PARAMETER User Name of the user to connect with. .PARAMETER Scope Scope in which the connection will be opened. .INPUTS None. You cannot pipe objects to Open-Portal. .OUTPUTS System.Void. None. .EXAMPLE PS> Open-Portal myconn .EXAMPLE PS> Open-Portal -Name myconn -Client SSH -User root -Scope Console #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the connection." )] [ValidateSet([ValidateSetConnectionName])] [ValidateConnectionName()] [string] $Name, [Parameter( HelpMessage = "Name of the client to use to initiate the connection." )] [ValidateSet([ValidateSetClientName])] [ValidateClientName()] [Alias("c")] [string] $Client, [Parameter( HelpMessage = "Name of the user to connect with." )] [Alias("u")] [string] $User, [Parameter( HelpMessage = "Scope in which the connection will be opened." )] [Alias("x")] [Scopes] $Scope ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Error import inventory: {0}" -f $_.Exception.Message ) } $Invocation = @{} $Invocation.Connection = $Inventory.GetConnection($Name) Write-Debug -Message ("Open connection {0}" -f $Invocation.Connection.ToString()) $Invocation.Client = if ($Client) { if (-not $Inventory.ClientExists($Client)) { Write-Error -Message ( "Cannot open connection with the specified client `"{0}`" because it does not exist." -f ( $Client ) ) } $Inventory.GetClient($Client) } else { if (-not $Inventory.ClientExists($Invocation.Connection.DefaultClient)) { Write-Error -Message ( "Cannot open connection with the default client `"{0}`" because it does not exist." -f ( $Invocation.Connection.DefaultClient ) ) } $Inventory.GetClient($Invocation.Connection.DefaultClient) } Write-Debug -Message ("Open connection with client {0}" -f $Invocation.Client.ToString()) $Invocation.Port = if ($Invocation.Connection.IsDefaultPort()) { $Invocation.Client.DefaultPort } else { $Invocation.Connection.Port } Write-Debug -Message ("Open connection on port {0}" -f $Invocation.Port) $Invocation.Executable = $Invocation.Client.Executable $Invocation.Arguments = if ($Invocation.Client.RequiresUser) { if ($User) { $Invocation.Client.GenerateArgs( $Invocation.Connection.Hostname, $Invocation.Port, $User ) } elseif ($Invocation.Connection.DefaultUser) { $Invocation.Client.GenerateArgs( $Invocation.Connection.Hostname, $Invocation.Port, $Invocation.Connection.DefaultUser ) } else { Write-Error -Message "Cannot open connection: A user must be specified." } } else { $Invocation.Client.GenerateArgs( $Invocation.Connection.Hostname, $Invocation.Port ) } $Invocation.Command = "{0} {1}" -f $Invocation.Executable, $Invocation.Arguments Write-Debug -Message ("Open connection with command `"{0}`"" -f $Invocation.Command) $Invocation.Scope = if ($Scope) { $Scope } else { $Invocation.Client.DefaultScope } Write-Debug -Message ("Open connection in scope `"{0}`"" -f $Invocation.Scope) if ($PSCmdlet.ShouldProcess($Invocation.Connection.ToString(), "Initiate connection")) { switch ($Invocation.Scope) { ([Scopes]::Console) { Invoke-Expression -Command $Invocation.Command } ([Scopes]::External) { Start-Process -FilePath $Invocation.Executable -ArgumentList $Invocation.Arguments } ([Scopes]::Undefined) { Write-Error -Message "Cannot open connection: Scope is undefined." } default { Write-Error -Message "Cannot open connection: Scope is unknown." } } } } } function Remove-Portal { <# .SYNOPSIS Removes Portal connection. .DESCRIPTION Removes connection entry from the Portal inventory file. .PARAMETER Name Name of the connection. .INPUTS None. You cannot pipe objects to Remove-Portal. .OUTPUTS System.Void. None. .EXAMPLE PS> Remove-Portal myconn .EXAMPLE PS> Remove-Portal -Name myconn #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the connection." )] [ValidateSet([ValidateSetConnectionName])] [ValidateConnectionName()] [string] $Name ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Cannot open inventory: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, "Remove Connection {0}" -f $Name ) ) { $Inventory.RemoveConnection($Name) try { $Inventory.SaveFile() Write-Verbose -Message ( "Connection `"{0}`" has been removed from the inventory." -f $Name ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Remove-PortalClient { <# .SYNOPSIS Removes Portal client. .DESCRIPTION Removes client entry from the Portal inventory file. .PARAMETER Name Name of the client. .INPUTS None. You cannot pipe objects to Remove-PortalClient. .OUTPUTS System.Void. None. .EXAMPLE PS> Remove-PortalClient SSH .EXAMPLE PS> Remove-PortalClient -Name SSH #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the client." )] [ValidateSet([ValidateSetClientName])] [ValidateClientName()] [string] $Name ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Cannot open inventory: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, "Remove Client {0}" -f $Name ) ) { $Inventory.RemoveClient($Name) try { $Inventory.SaveFile() Write-Verbose -Message ( "Client `"{0}`" has been removed from the inventory." -f $Name ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Rename-Portal { <# .SYNOPSIS Renames Portal connection. .DESCRIPTION Renames connection entry from the Portal inventory file. .PARAMETER Name Name of the connection to rename. .PARAMETER NewName New name for the connection. .INPUTS None. You cannot pipe objects to Rename-Portal. .OUTPUTS System.Void. None. .EXAMPLE PS> Rename-Portal -Name my_old_conn -NewName my_new_conn #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the connection to rename." )] [ValidateSet([ValidateSetConnectionName])] [ValidateConnectionName()] [string] $Name, [Parameter( Position = 1, Mandatory = $true, HelpMessage = "New name for the connection." )] [ValidateNotNullOrEmpty()] [string] $NewName ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Cannot open inventory: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, ("Rename Connection {0} to {1}" -f $Name, $NewName) ) ) { $Inventory.RenameConnection($Name, $NewName) try { $Inventory.SaveFile() Write-Verbose -Message ( "Connection `"{0}`" has been renamed `"{1}`" in the inventory." -f $Name, $NewName ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Rename-PortalClient { <# .SYNOPSIS Renames Portal client. .DESCRIPTION Renames client entry from the Portal inventory file. .PARAMETER Name Name of the client to rename. .PARAMETER NewName New name for the client. .INPUTS None. You cannot pipe objects to Rename-PortalClient. .OUTPUTS System.Void. None. .EXAMPLE PS> Rename-PortalClient -Name my_old_client -NewName my_new_client #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the client to rename." )] [ValidateSet([ValidateSetClientName])] [ValidateClientName()] [string] $Name, [Parameter( Position = 1, Mandatory = $true, HelpMessage = "New name for the client." )] [ValidateNotNullOrEmpty()] [string] $NewName ) begin { $ErrorActionPreference = "Stop" } process { try { $Inventory = Import-Inventory } catch { Write-Error -Message ( "Cannot open inventory: {0}" -f $_.Exception.Message ) } if ($PSCmdlet.ShouldProcess( "Inventory file {0}" -f $Inventory.Path, ("Rename Client {0} to {1}" -f $Name, $NewName) ) ) { $Inventory.RenameClient($Name, $NewName) try { $Inventory.SaveFile() Write-Verbose -Message ( "Client `"{0}`" has been renamed `"{1}`" in the inventory." -f $Name, $NewName ) } catch { Write-Error -Message ( "Cannot save inventory: {0}" -f $_.Exception.Message ) } } } } function Set-PortalInventory { <# .SYNOPSIS Sets Portal inventory path. .DESCRIPTION Sets the specific environment variable to overwrite default path to the Portal inventory file. .PARAMETER Name Path to the inventory file. This path is set in a environment variable. Pass an empty string or null to reset to the default path. .PARAMETER Target Target scope where the environment variable will be saved. .INPUTS None. You cannot pipe objects to Set-PortalInventory. .OUTPUTS System.Void. None. .EXAMPLE PS> Set-PortalInventory C:\MyCustomInventory.json .EXAMPLE PS> Set-PortalInventory -Path C:\MyCustomInventory.json #> [OutputType([void])] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Path to the inventory file." )] [AllowEmptyString()] [string] $Path, [Parameter( HelpMessage = "Target scope of the environment variable." )] [ValidateSet("Process", "User")] [string] $Target = "User" ) process { $EnvVar = [Inventory]::EnvVariable if ($PSCmdlet.ShouldProcess( ("{0} environment variable {1}" -f $Target, $EnvVar), "Set value {0}" -f $Path ) ) { [System.Environment]::SetEnvironmentVariable( $EnvVar, $Path, [System.EnvironmentVariableTarget]::"$Target" ) Write-Verbose -Message ("{0} environment variable `"{1}`" has been set to `"{2}`"." -f $Target, $EnvVar, $Path) } } } function Test-Portal { <# .SYNOPSIS Tests Portal connection. .DESCRIPTION Tests Portal connection which is defined in the inventory. .PARAMETER Name Name of the connection. .INPUTS None. You cannot pipe objects to Test-Portal. .OUTPUTS System.String. Test-Portal returns a string with the status of the remote host. .EXAMPLE PS> Test-Portal myconn (status) .EXAMPLE PS> Test-Portal -Name myconn (status) #> [OutputType([string])] [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, HelpMessage = "Name of the connection." )] [ValidateSet([ValidateSetConnectionName])] [ValidateConnectionName()] [string] $Name ) process { $Inventory = Import-Inventory $Status = "Unknown" $Connection = $Inventory.GetConnection($Name) $Port = if ($Connection.IsDefaultPort()) { $Inventory.GetClient($Connection.DefaultClient).DefaultPort } else { $Connection.Port } $TestConnectionParams = @{ TargetName = $Connection.Hostname TcpPort = $Port TimeoutSeconds = 3 ErrorAction = "Stop" } try { if (Test-Connection @TestConnectionParams) { Write-Information -MessageData ( "Connection {0} is up on port {1}." -f $Connection.ToString(), $Port ) -InformationAction Continue $Status = "Online" } else { Write-Error -Message ( "Connection: {0} is down on port {1}." -f $Connection.ToString(), $Port ) $Status = "Offline" } } catch { Write-Error -Message $_.Exception.Message $Status = "CriticalFailure" } $Status } } #endregion Public functions |