MyRemoteManager.psm1

#
# Script module for module 'MyRemoteManager'
#

#Requires -Version 6.0
#Requires -PSEdition Core

using namespace System.Management.Automation


#region Classes
class Item {
    # Name
    [ValidateLength(1, 30)]
    [ValidatePattern("^([a-zA-Z0-9_\-]+)$")]
    [string] $Name
    # Description
    [string] $Description

    [hashtable] Splat() {
        $Hashtable = @{}
        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

    Client(
        [string] $Name,
        [string] $Executable,
        [string] $TokenizedArgs,
        [UInt16] $DefaultPort,
        [string] $Description
    ) {
        $this.Name = $Name
        $this.Executable = $Executable
        [Client]::ValidateTokenizedArgs($TokenizedArgs)
        $this.TokenizedArgs = $TokenizedArgs
        $this.DefaultPort = $DefaultPort
        $this.Description = $Description
    }

    static [void] ValidateTokenizedArgs([string] $TokenizedArgs) {
        "host", "port" | ForEach-Object -Process {
            if ($TokenizedArgs -notmatch ("<{0}>" -f $_)) {
                throw "The command does not contain the following token: {0}" -f $_
            }
        }
    }

    static [bool] UserTokenExists([string] $TokenizedArgs) {
        return $(if ($TokenizedArgs -match "<user>") { $true } else { $false })
    }

    [string] ToString() {
        return "{0} ({1}): {2} {3}" -f `
            $this.Name, `
            $this.Description, `
            $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
    # Client
    [Client] $Client

    Connection(
        [String] $Name,
        [String] $Hostname,
        [UInt16] $Port,
        [Client] $Client,
        [string] $Description
    ) {
        $this.Name = $Name
        $this.Hostname = $Hostname.ToLower()
        $this.Port = $Port
        $this.Client = $Client
        $this.Description = $Description
    }

    [string] GenerateArgs() {
        return $this.Client.TokenizedArgs.Replace(
            "<host>", $this.Hostname
        ).Replace(
            "<port>", $(if ($this.Port -eq 0) { $this.Client.DefaultPort } else { $this.Port })
        )
    }

    [string] GenerateArgs([string] $User) {
        return $this.GenerateArgs().Replace(
            "<user>", $User
        )
    }

    [void] Invoke() {
        $FilePath = $this.Client.Executable
        if ([Client]::UserTokenExists($this.Client.TokenizedArgs)) {
            $User = Read-Host -Prompt ("Username" -f $this.Hostname)
            $Arguments = $this.GenerateArgs($User)
        }
        else {
            $Arguments = $this.GenerateArgs()
        }
        Start-Process -FilePath $FilePath -ArgumentList $Arguments
    }

    [string] ToString() {
        return "{0} ({1}): {2} to {3}:{4}" -f `
            $this.Name, `
            $this.Description, `
            $this.Client.Name, `
            $this.Hostname, `
            $this.Port.ToString().Replace("0", "default")
    }
}
class Inventory {
    # 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 = "MY_RM_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 $env:HOME -ChildPath "MyRemoteManager.json"
    }

    [void] ReadFile() {
        $Items = Get-Content -Path $this.Path -Raw -Encoding ([Inventory]::Encoding) | ConvertFrom-Json -AsHashtable
        foreach ($c in $Items.Clients) {
            $this.Clients += New-Object -TypeName Client -ArgumentList @(
                $c.Name,
                $c.Executable,
                $c.TokenizedArgs,
                $c.DefaultPort,
                $c.Description
            )
        }
        foreach ($c in $Items.Connections) {
            $Client = $this.Clients | Where-Object -Property Name -EQ $c.Client
            $this.Connections += New-Object -TypeName Connection -ArgumentList @(
                $c.Name,
                $c.Hostname,
                $c.Port,
                $Client,
                $c.Description
            )
        }
    }

    [void] SaveFile() {
        $Items = @{ Clients = @(); Connections = @() }
        foreach ($c in $this.Clients) {
            $Items.Clients += $c.Splat()
        }
        foreach ($c in $this.Connections) {
            $Connection = $c.Splat()
            $Connection.Client = $Connection.Client.Name
            $Items.Connections += $Connection
        }
        $Json = ConvertTo-Json -InputObject $Items -Depth 3
        $BackupPath = "{0}.backup" -f $this.Path
        if (Test-Path -Path $this.Path) {
            Copy-Item -Path $this.Path -Destination $BackupPath -Force
        }
        Set-Content -Path $this.Path -Value $Json -Encoding ([Inventory]::Encoding) -Force
    }

    [bool] ClientExists([string] $Name) {
        return $(if (($this.Clients | Where-Object -Property Name -EQ $Name).Count -gt 0) { $true } else { $false })
    }

    [bool] ConnectionExists([string] $Name) {
        return $(if (($this.Connections | Where-Object -Property Name -EQ $Name ).Count -gt 0) { $true } else { $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
    }

    [void] AddClient([Client] $Client) {
        if ($this.ClientExists($Client.Name)) {
            throw "Cannot add Client `"{0}`" as it already exists." -f $Client.Name
        }
        else {
            $this.Clients += $Client
        }
    }

    [void] AddConnection([Connection] $Connection) {
        if ($this.ConnectionExists($Connection.Name)) {
            throw "Cannot add Connection `"{0}`" as it already exists." -f $Connection.Name
        }
        else {
            $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
    }
}
class ValidateClientName : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
        return $Inventory.Clients | ForEach-Object -Process { $_.Name }
    }
}
class ValidateConnectionName : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
        return $Inventory.Connections | ForEach-Object -Process { $_.Name }
    }
}
#endregion Classes

#region Public functions
function Add-MyRMClient {

    <#
    .SYNOPSIS
        Adds MyRemoteManager client.
    .DESCRIPTION
        Adds client entry to the MyRemoteManager 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 MyRemoteManager.
    .PARAMETER DefaultPort
        Network port to use if the connection has no defined port.
    .PARAMETER Description
        Short description for the client.
    .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 Add-MyRMClient.
    .OUTPUTS
        System.Void. None.
            or if PassThru is set,
        System.String. Add-MyRMClient returns a string with the name of the added client.
    .EXAMPLE
        PS> Add-MyRMClient -Name SSH -Executable "ssh.exe" -Arguments "-l <user> -p <port> <host>" -DefaultPort 22
    .EXAMPLE
        PS> Add-MyRMClient -Name MyCustomClient -Executable "client.exe" -Arguments "--hostname <host> --port <port>" -DefaultPort 666 -Description "My custom client"
    .EXAMPLE
        PS> Add-MyRMClient -Name SSH -Executable "ssh.exe" -Arguments "-l <user> -p <port> <host>" -DefaultPort 22 -PassThru
        SSH
    #>


    [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 = "Short description of the client."
        )]
        [string] $Description
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $Client = New-Object -TypeName Client -ArgumentList @(
            $Name,
            $Executable,
            $Arguments,
            $DefaultPort,
            $Description
        )
        if (
            $PSCmdlet.ShouldProcess(
                "Inventory file {0}" -f $Inventory.Path,
                "Add Client {0}" -f $Client.ToString()
            )
        ) {
            $Inventory.AddClient($Client)
            $Inventory.SaveFile()
            Write-Verbose -Message ("Client `"{0}`" has been added to the inventory." -f $Name)
        }
    }
    end {}
}
function Add-MyRMConnection {

    <#
    .SYNOPSIS
        Adds MyRemoteManager connection.
    .DESCRIPTION
        Adds connection entry to the MyRemoteManager 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 Client
        Name of the client.
    .PARAMETER Description
        Short description for the connection.
    .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 Add-MyRMConnection.
    .OUTPUTS
        System.Void. None.
            or if PassThru is set,
        System.String. Add-MyRMConnection returns a string with the name of the added connection.
    .EXAMPLE
        PS> Add-MyRMConnection -Name myconn -Hostname myhost -Client SSH
    .EXAMPLE
        PS> Add-MyRMConnection -Name myconn -Hostname myhost -Port 2222 -Client SSH -Description "My connection"
    .EXAMPLE
        PS> Add-MyRMConnection -Name myconn -Hostname myhost -Client SSH -PassThru
        myconn
    #>


    [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 = "Client to use to connect to the remote host."
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( [ValidateClientName] )]
        [string] $Client,

        [Parameter(
            HelpMessage = "Short description of the connection."
        )]
        [string] $Description
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $Connection = New-Object -TypeName Connection -ArgumentList @(
            $Name,
            $Hostname,
            $Port,
            $Inventory.GetClient($Client),
            $Description
        )
        if ($PSCmdlet.ShouldProcess(
                "Inventory file {0}" -f $Inventory.Path,
                "Add Connection {0}" -f $Connection.ToString()
            )
        ) {
            $Inventory.AddConnection($Connection)
            $Inventory.SaveFile()
            Write-Verbose -Message ("Connection `"{0}`" has been added to the inventory." -f $Name)
        }
    }
    end {}
}
function Get-MyRMClient {

    <#
    .SYNOPSIS
        Gets MyRemoteManager clients.
    .DESCRIPTION
        Gets available clients from the MyRemoteManager inventory file.
        Clients can be filtered by their name.
    .PARAMETER Name
        Filters clients by name.
    .INPUTS
        None. You cannot pipe objects to Get-MyRMClient.
    .OUTPUTS
        PSCustomObject. Get-MyRMClient returns objects with details of the available clients.
    .EXAMPLE
        PS> Get-MyRMClient
        (shows objects)
    .EXAMPLE
        PS> Get-MyRMClient -Name "custom_*"
        (shows filtered objects)
    #>


    [OutputType([PSCustomObject[]])]
    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = "Filter by client name."
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Name = "*"
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $Clients = @()
        foreach ($c in $Inventory.Clients) {
            $Clients += [PSCustomObject] @{
                Name        = $c.Name
                Command     = "{0} {1}" -f $c.Executable, $c.TokenizedArgs
                DefaultPort = $c.DefaultPort
                Description = $c.Description
            }
        }
    }
    end {
        $Clients
        | Where-Object -Property Name -Like $Name
        | Sort-Object -Property Name
    }
}
function Get-MyRMConnection {

    <#
    .SYNOPSIS
        Gets MyRemoteManager connections.
    .DESCRIPTION
        Gets available connections from the MyRemoteManager 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-MyRMConnection.
    .OUTPUTS
        PSCustomObject. Get-MyRMConnection returns objects with details of the available connections.
    .EXAMPLE
        PS> Get-MyRMConnection
        (shows objects)
    .EXAMPLE
        PS> Get-MyRMConnection -Name "myproject_*" -Client "*_myproject"
        (shows filtered objects)
    #>


    [OutputType([PSCustomObject[]])]
    [CmdletBinding()]
    param (
        [Parameter(
            HelpMessage = "Filter by connection name."
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Name = "*",

        [Parameter(
            HelpMessage = "Filter by client name."
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Client = "*"
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $Connections = @()
        foreach ($c in $Inventory.Connections) {
            $Connections += [PSCustomObject] @{
                Name        = $c.Name
                Hostname    = $c.Hostname
                Port        = if ($c.Port -eq 0) {
                    $c.Client.DefaultPort
                }
                else {
                    $c.Port
                }
                Client      = $c.Client.Name
                Description = $c.Description
            }
        }
    }
    end {
        $Connections
        | Where-Object -Property Name -Like $Name
        | Where-Object -Property Client -Like $Client
        | Sort-Object -Property Name
    }
}
function Get-MyRMInventoryInfo {

    <#
    .SYNOPSIS
        Gets MyRemoteManager inventory information.
    .DESCRIPTION
        Gets detailed information about the MyRemoteManager inventory.
    .INPUTS
        None. You cannot pipe objects to Get-MyRMInventoryInfo.
    .OUTPUTS
        PSCustomObject. Get-MyRMInventoryInfo returns an object with detailed information.
    .EXAMPLE
        PS> Get-MyRMInventoryInfo
        (shows object)
    #>


    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param ()
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $InventoryInfo = [PSCustomObject] @{
            Path                = $Inventory.Path
            EnvVariable         = [Inventory]::EnvVariable
            NumberOfClients     = $Inventory.Clients.Count
            NumberOfConnections = $Inventory.Connections.Count
        }
    }
    end {
        $InventoryInfo
    }
}
function Invoke-MyRMConnection {

    <#
    .SYNOPSIS
        Invokes MyRemoteManager connection.
    .DESCRIPTION
        Invokes MyRemoteManager connection which is defined in the inventory.
    .PARAMETER Name
        Name of the connection.
    .INPUTS
        None. You cannot pipe objects to Invoke-MyRMConnection.
    .OUTPUTS
        System.Void. None.
    .EXAMPLE
        PS> Invoke-MyRMConnection myconn
    .EXAMPLE
        PS> Invoke-MyRMConnection -Name myconn
    #>


    [OutputType([void])]
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = "Name of the connection."
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( [ValidateConnectionName] )]
        [string] $Name
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        $Connection = $Inventory.Connections | Where-Object -Property Name -EQ $Name
        if ($PSCmdlet.ShouldProcess($Connection.ToString(), "Initiate connection")) {
            $Connection.Invoke()
        }
    }
    end {}
}
function New-MyRMInventory {

    <#
    .SYNOPSIS
        Creates MyRemoteManager inventory file.
    .DESCRIPTION
        Creates a new inventory file where MyRemoteManager 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-MyRMInventory.
    .OUTPUTS
        System.Void. None.
            or if PassThru is set,
        System.String. New-MyRMInventory returns a string with the path to the created inventory.
    .EXAMPLE
        PS> New-MyRMInventory
    .EXAMPLE
        PS> New-MyRMInventory -NoDefaultClients
    .EXAMPLE
        PS> New-MyRMInventory -Force
    .EXAMPLE
        PS> New-MyRMInventory -PassThru
        C:\Users\MyUsername\MyRemoteManager.json
    .EXAMPLE
        PS> New-MyRMInventory -NoDefaultClients -Force -PassThru
        C:\Users\MyUsername\MyRemoteManager.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 {
        $Inventory = New-Object -TypeName Inventory
    }
    process {
        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) {
                $Inventory.AddClient(
                    (New-Object -TypeName Client -ArgumentList @(
                            "OpenSSH",
                            "C:\Windows\System32\OpenSSH\ssh.exe",
                            "-l <user> -p <port> <host>",
                            22,
                            "OpenSSH (Microsoft Windows feature)"
                        )
                    )
                )
                $Inventory.AddClient(
                    (New-Object -TypeName Client -ArgumentList @(
                            "PuTTY_SSH",
                            "putty.exe",
                            "-ssh -P <port> <user>@<host>",
                            22,
                            "PuTTY using SSH protocol"
                        )
                    )
                )
                $Inventory.AddClient(
                    (New-Object -TypeName Client -ArgumentList @(
                            "RD",
                            "C:\Windows\System32\mstsc.exe",
                            "/v:<host>:<port> /fullscreen",
                            3389,
                            "Microsoft Remote Desktop"
                        )
                    )
                )
            }
            $Inventory.SaveFile()
            Write-Verbose -Message ("Inventory file has been created: {0}" -f $Inventory.Path)
        }
    }
    end {
        if ($PassThru.IsPresent) {
            Resolve-Path $Inventory.Path | Select-Object -ExpandProperty Path
        }
    }
}
function Remove-MyRMClient {

    <#
    .SYNOPSIS
        Removes MyRemoteManager client.
    .DESCRIPTION
        Removes client entry from the MyRemoteManager inventory file.
    .PARAMETER Name
        Name of the client.
    .INPUTS
        None. You cannot pipe objects to Remove-MyRMClient.
    .OUTPUTS
        System.Void. None.
    .EXAMPLE
        PS> Remove-MyRMClient SSH
    .EXAMPLE
        PS> Remove-MyRMClient -Name SSH
    #>


    [OutputType([void])]
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = "Name of the client."
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( [ValidateClientName] )]
        [string] $Name
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        if (
            $PSCmdlet.ShouldProcess(
                "Inventory file {0}" -f $Inventory.Path,
                "Remove Client {0}" -f $Name
            )
        ) {
            $Inventory.RemoveClient($Name)
            $Inventory.SaveFile()
            Write-Verbose -Message ("Client `"{0}`" has been removed from the inventory." -f $Name)
        }
    }
    end {}
}
function Remove-MyRMConnection {

    <#
    .SYNOPSIS
        Removes MyRemoteManager connection.
    .DESCRIPTION
        Removes connection entry from the MyRemoteManager inventory file.
    .PARAMETER Name
        Name of the connection.
    .INPUTS
        None. You cannot pipe objects to Remove-MyRMConnection.
    .OUTPUTS
        System.Void. None.
    .EXAMPLE
        PS> Remove-MyRMConnection myconn
    .EXAMPLE
        PS> Remove-MyRMConnection -Name myconn
    #>


    [OutputType([void])]
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = "Name of the connection."
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( [ValidateConnectionName] )]
        [string] $Name
    )
    begin {
        $Inventory = New-Object -TypeName Inventory
        $Inventory.ReadFile()
    }
    process {
        if (
            $PSCmdlet.ShouldProcess(
                "Inventory file {0}" -f $Inventory.Path,
                "Remove Connection {0}" -f $Name
            )
        ) {
            $Inventory.RemoveConnection($Name)
            $Inventory.SaveFile()
            Write-Verbose -Message ("Connection `"{0}`" has been removed from the inventory." -f $Name)
        }
    }
    end {}
}
function Set-MyRMInventoryPath {

    <#
    .SYNOPSIS
        Sets MyRemoteManager inventory path.
    .DESCRIPTION
        Sets the specific environment variable to overwrite default path to the MyRemoteManager 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-MyRMInventoryPath.
    .OUTPUTS
        System.Void. None.
    .EXAMPLE
        PS> Set-MyRMInventoryPath C:\MyCustomInventory.json
    .EXAMPLE
        PS> Set-MyRMInventoryPath -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"
    )
    begin {
        $EnvVar = [Inventory]::EnvVariable
    }
    process {
        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)
        }
    }
    end {}
}
#endregion Public functions