Sandbox.psm1

# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Library
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Enum SandboxCommandType
{
    PowershellScript = 1
    CmdScript = 2
}

Class SandboxLogonCommand
{

    [System.UInt16] $Index
    [System.String] $Command
    [SandboxCommandType] $Type
    [System.String] $Description

    SandboxLogonCommand(
        [System.UInt16]$Index,
        [System.String]$Command,
        [SandboxCommandType]$Type,
        [System.String]$Description
    )
    {
        $this.Index = $Index;
        $this.Command = $Command;
        $this.Type = $Type;
        $this.Description = $Description;
    }

}

Class SandboxMappedFolder
{

    [System.IO.DirectoryInfo]$HostFolder
    [System.IO.DirectoryInfo]$RemoteFolder
    [System.Boolean]$ReadOnly
    hidden [System.String]$RemoteBasePath = 'C:\Users\WDAGUtilityAccount\Desktop'

    SandboxMappedFolder(
        [System.String]$HostFolder,
        [System.Boolean]$ReadOnly
    )
    {
        $This.HostFolder = [System.IO.DirectoryInfo]::new($HostFolder)
        $This.RemoteFolder = [System.IO.DirectoryInfo]::new([System.IO.Path]::Combine($this.RemoteBasePath, $this.HostFolder.Name))
        $This.ReadOnly = $ReadOnly;
    }

}

Enum SandboxStatus
{
    Enable = 1
    Disable = 0
}

Class SandboxConfig
{
    #Region Properties
    # Enable or disable networking in the sandbox
    hidden [SandboxStatus]$Networking

    # Enable or disable the virtualized GPU
    hidden [SandboxStatus]$VGpu

    # List of script or program executions at startup
    hidden [System.Collections.Generic.List[SandboxLogonCommand]]$LogonCommand

    # List of shared folders of the host
    hidden [System.Collections.Generic.List[SandboxMappedFolder]]$MappedFolder
    #endregion

    #region Constructors
    SandboxConfig()
    {
        $this.Networking = [SandboxStatus]::Enable
        $this.VGpu = [SandboxStatus]::Disable
        $this.LogonCommand = [System.Collections.Generic.List[SandboxLogonCommand]]::new()
        $this.MappedFolder = [System.Collections.Generic.List[SandboxMappedFolder]]::new()
    }

    SandboxConfig(
        [SandboxStatus]$Networking,
        [SandboxStatus]$VGpu
    )
    {
        $this.Networking = [SandboxStatus]::Enable
        $this.VGpu = [SandboxStatus]::Disable
        $this.LogonCommand = [System.Collections.Generic.List[SandboxLogonCommand]]::new()
        $this.MappedFolder = [System.Collections.Generic.List[SandboxMappedFolder]]::new()
    }
    #endregion

    #region Config
    [System.Object] GetConfig()
    {
        return [PSCustomObject]@{
            Networking   = $this.Networking
            VGpu         = $this.VGpu
            LogonCommand = $this.LogonCommand
            MappedFolder = $this.MappedFolder
        }
    }
    #endregion

    #region Networking
    [SandboxStatus] GetNetworking() { return $this.Networking }
    [System.Void] SetNetworking([SandboxStatus]$Networking) { $this.Networking = $Networking }
    #endregion

    #region VGpu
    [SandboxStatus] GetVGpu() { return $this.VGpu }
    [System.Void] SetVGpu([SandboxStatus]$VGpu) { $this.VGpu = $VGpu }
    #endregion

    #region MappedFolder
    [System.Collections.Generic.List[SandboxMappedFolder]] GetMappedFolder() { return $this.MappedFolder }

    [SandboxMappedFolder] GetMappedFolder([System.String]$HostFolder)
    {
        return $this.MappedFolder.Where( { $_.HostFolder.FullName -eq $HostFolder })[0]
    }

    hidden [System.Boolean] TestMappedFolderName([String]$HostFolder)
    {
        return [System.Convert]::ToBoolean(
            ($this.MappedFolder.Where(
                    { $_.HostFolder.Name -eq [System.IO.DirectoryInfo]::new($HostFolder).name } )
            ).count
        )
    }

    [System.Void] AddMappedFolder([System.String]$HostFolder)
    {
        $this.AddMappedFolder($HostFolder, $true)
    }

    [System.Void] AddMappedFolder(
        [System.String]$HostFolder,
        [System.Boolean]$ReadOnly
    )
    {
        if ($this.GetMappedFolder($HostFolder).count -eq 0)
        {
            if ($this.TestMappedfolderName($HostFolder) -eq $false)
            {
                $item = [SandboxMappedFolder]::new($HostFolder, $ReadOnly)
                $this.MappedFolder.Add($item)
            }
            else
            {
                Throw "The destination folder name already in the configuration."
            }
        }
        else
        {
            Throw "The path '${HostFolder}' is already exists in the configuration."
        }
    }

    [System.Void] RemoveMappedFolder([System.String]$HostFolder)
    {
        [SandboxMappedFolder] $ItemHostFolder = $this.GetMappedFolder($HostFolder);
        if ($ItemHostFolder -ne $null)
        {
            $this.MappedFolder.Remove($ItemHostFolder);
        }
        else
        {
            Throw "Could not remove path '${HostFolder}' because it does not exist."
        }
    }

    [System.Void] ClearMappedFolder()
    {
        $this.MappedFolder = [System.Collections.Generic.List[SandboxMappedFolder]]::new()
    }
    #endregion

    #region LogonCommand
    [System.Collections.Generic.List[SandboxLogonCommand]] GetLogonCommand() { return $this.LogonCommand }

    [SandboxLogonCommand] GetLogonCommandByIndex([System.Int16]$Index)
    {
        return $this.LogonCommand.Where( { $_.Index -eq $Index })[0]
    }

    [SandboxLogonCommand] GetLogonCommandByCommand([System.String]$Command)
    {
        return $this.LogonCommand.Where( { $_.Command -eq $Command })[0]
    }

    [SandboxLogonCommand[]] GetLogonCommandByType([SandboxCommandType]$Type)
    {
        return $this.LogonCommand.Where( { $_.Type -eq [SandboxCommandType]$Type })
    }

    [System.Void] ClearLogonCommand()
    {
        $this.LogonCommand = [System.Collections.Generic.List[SandboxLogonCommand]]::new()
    }

    hidden [System.Int16] GetLogonCommandNextIndex()
    {
        if ($this.LogonCommand.count -gt 0)
        {
            # TODO : Convert the line below to .net ( .sort() )
            $nextIndex = ($this.LogonCommand.Index | Sort-Object -Descending)[0] + 1
            return $nextIndex
        }
        else
        {
            return 1
        }
    }

    [System.Void] AddLogonCommand(
        [System.String]$Command,
        [SandboxCommandType]$Type
    )
    {
        [System.UInt16]$index = $this.GetLogonCommandNextIndex()
        $this.AddLogonCommand($index, $Command, $Type, '')
    }

    [System.Void] AddLogonCommand(
        [System.String]$Command,
        [SandboxCommandType]$Type,
        [System.String]$Description
    )
    {
        [System.UInt16]$index = $this.GetLogonCommandNextIndex()
        $this.AddLogonCommand($index, $Command, $Type, $Description)
    }

    [System.Void] AddLogoncommand(
        [system.UInt16]$Index,
        [System.String]$Command,
        [SandboxCommandType]$Type,
        [System.String]$Description
    )
    {
        if ( $this.GetLogonCommandByIndex($Index).count -eq 0 )
        {
            if ( $this.GetLogonCommandByCommand($Command).count -eq 0 )
            {
                $logonCommandItem = [SandboxLogonCommand]::new($Index, $Command, $Type, $Description)
                $this.LogonCommand.add($logonCommandItem)
            }
            else
            {
                Throw "Could not add command '${Command}' because is already exist in configuration."
            }
        }
        else
        {
            Throw "Could not add command because this index '${Index}' is already exist in configuration."
        }
    }

    [System.Void] RemoveLogonCommand([System.UInt16]$Index)
    {
        $commandItem = $this.GetLogonCommandByIndex($Index)
        if ($commandItem)
        {
            $this.LogonCommand.Remove($commandItem)
        }
        else
        {
            Throw "Could not remove command because this index '${Index}' does not exist."
        }
    }
    #endregion

    #region
    [System.Void] ExportToWsb([System.String]$Path)
    {
        # Xml Settings
        [System.Xml.XmlWriterSettings] $xmlWriterSettings = [System.Xml.XmlWriterSettings]::new()
        $xmlWriterSettings.Encoding = [System.Text.Encoding]::UTF8
        $xmlWriterSettings.OmitXmlDeclaration = $true
        $xmlWriterSettings.Indent = $true
        $xmlWriterSettings.IndentChars = ' '

        # Get an XMLTextWriter to create the XML
        [System.Xml.XmlWriter] $XmlWriter = [System.Xml.XmlWriter]::Create($Path, $xmlWriterSettings)

        # Create root element "Configuration"
        $xmlWriter.WriteStartDocument()
        $xmlWriter.WriteStartElement("Configuration")

        # Networking
        $xmlWriter.WriteStartElement("Networking")
        $xmlWriter.WriteRaw($this.Networking.ToString())
        $xmlWriter.WriteEndElement()

        # VGpu
        $xmlWriter.WriteStartElement("VGpu")
        $xmlWriter.WriteRaw($this.VGpu.ToString())
        $xmlWriter.WriteEndElement()

        # MappedFolder
        if ($this.MappedFolder.Count -gt 0)
        {
            $xmlWriter.WriteStartElement("MappedFolders");
            foreach ($mappedFolderItem in $this.MappedFolder)
            {
                $xmlWriter.WriteStartElement("MappedFolder")
                $xmlWriter.WriteStartElement("HostFolder")
                $xmlWriter.WriteRaw($mappedFolderItem.HostFolder.FullName)
                $xmlWriter.WriteEndElement()
                $xmlWriter.WriteStartElement("ReadOnly")
                $xmlWriter.WriteRaw($mappedFolderItem.ReadOnly.ToString().ToLower())
                $xmlWriter.WriteEndElement()
                $xmlWriter.WriteEndElement()
            }
            $xmlWriter.WriteEndElement()
        }

        # LogonCommand
        # TODO : Convert Sort-Object to .net class
        if ($this.LogonCommand.Count -gt 0)
        {
            $xmlWriter.WriteStartElement("LogonCommand")
            foreach ($logonCommandItem in ($this.LogonCommand | Sort-Object -Property Index) )
            {
                $xmlWriter.WriteStartElement("Command")
                $xmlWriter.WriteRaw($logonCommandItem.Command)
                $xmlWriter.WriteEndElement()
            }
            $xmlWriter.WriteEndElement()
        }

        #Close the "Configuration" node:
        $xmlWriter.WriteEndElement()

        # Finalize the document:
        $xmlWriter.WriteEndDocument()
        $xmlWriter.Flush()
        $xmlWriter.Close()
        $xmlWriter.Dispose()
    }
    #endregion
}

Class SandboxService
{

    # Windows Feature Name
    [System.String]$WindowsFeatureName = 'Containers-DisposableClientVM'

    #region constructor
    SandboxService()
    { }
    #endregion

    #region Windows Feature
    [System.Void]EnableWindowsFeature()
    {
        if ($this.TestElevatedSession())
        {
            if ($this.TestWindowsFeature() -eq $false)
            {
                try
                {
                    # Enable Windows Feature
                    $splats = @{
                        FeatureName = $this.WindowsFeatureName
                        Online      = $true
                        ErrorAction = 'Stop'
                    }
                    Enable-WindowsOptionalFeature @splats
                    Write-Verbose "Successfully enabled '$($this.WindowsFeatureName)'"
                }
                catch
                {
                    throw "Failed to enable '$($this.WindowsFeatureName)'."
                }
            }
            else
            {
                throw "Unable to enable '$($this.WindowsFeatureName)' because is already enabled."
            }
        }
        else
        {
            throw "Unable to enable '$($this.WindowsFeatureName)' because youd need to have an elevation."
        }
    }

    [System.Void]DisableWindowsFeature()
    {
        if ($this.TestElevatedSession())
        {
            if ($this.TestWindowsFeature())
            {
                try
                {
                    # Disable Windows Feature
                    $splats = @{
                        FeatureName = $this.WindowsFeatureName
                        Online      = $true
                        ErrorAction = 'Stop'
                    }
                    Disable-WindowsOptionalFeature @splats
                    Write-Verbose "Successfully disabled '$($this.WindowsFeatureName)'"
                }
                catch
                {
                    throw "Failed to disable '$($this.WindowsFeatureName)'."
                }
            }
            else
            {
                throw "Unable disable '$($this.WindowsFeatureName)' because is already disabled."
            }
        }
        else
        {
            throw "Unable to disable '$($this.WindowsFeatureName)' because youd need to have an elevation."
        }
    }

    hidden [System.Boolean]TestWindowsFeature()
    {
        if ($this.TestElevatedSession())
        {
            # Get the state of the Windows Feature
            $windowsFeatureState = [System.String]::Empty
            try
            {
                $windowsFeatureState = (Get-WindowsOptionalFeature -FeatureName $this.WindowsFeatureName -Online).State
            }
            catch
            {
                throw "Failed to get the state of '$($this.WindowsFeatureName)'."
            }

            # Verify if the Windows Feature is enabled
            if ($windowsFeatureState -eq "Enabled")
            {
                return $true
            }
            else
            {
                return $false
            }
        }
        else
        {
            throw "Unable to test '$($this.WindowsFeatureName)' because youd need to have an elevation."
        }
    }
    #endregion

    #region Tools
    hidden [System.Boolean]TestElevatedSession()
    {
        $currentIdentity = [Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent())
        return $currentIdentity.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    #enregion

}

# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# + Public Function
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
function Get-SandboxClass
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet('Config', 'Service')]
        [System.String]
        $Name,

        [Parameter(
            Position = 1,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Switch]
        $Cache
    )

    begin
    {
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"
    }

    process
    {
        if ($Cache)
        {
            $sandboxObject = Get-Variable -Name "Sandbox${Name}" -Scope 'Script' -ErrorAction SilentlyContinue -ValueOnly
            if ( $null -eq $sandboxObject )
            {
                $sandboxObject = New-SandboxClass -Name $Name
                Set-Variable -Name "Sandbox${Name}" -Value $sandboxObject -Scope 'Script'
                return $sandboxObject
            }
            return $sandboxObject
        }
        else
        {
            New-SandboxClass -Name $Name
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function New-SandboxClass
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet('Config', 'Service')]
        [System.String]
        $Name
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"
    }

    process
    {
        return New-Object -TypeName "Sandbox${Name}"
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }



}

function Export-SandboxConfig
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Path
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        try
        {
            $config.ExportToWsb($Path)
        }
        catch
        {
            Write-Error $_.Exception.Message
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Disable-Sandbox
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the service instance of the Sandbox class
        $service = Get-SandboxClass -Name 'Service'
    }

    process
    {
        $service.DisableWindowsFeature()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Enable-Sandbox
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the service instance of the Sandbox class
        $service = Get-SandboxClass -Name 'Service'
    }

    process
    {
        $service.EnableWindowsFeature()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Add-SandboxLogonCommand
{
    [CmdletBinding( DefaultParameterSetName = 'Default' )]
    [OutputType('System.Void')]
    param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Index'
        )]
        [System.UInt16]
        $Index,

        [Parameter(
            Position = 1,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Command,

        [Parameter(
            Position = 2,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [SandboxCommandType]
        $type = 'PowershellScript',

        [Parameter(
            Position = 3,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Description = [System.String]::Empty
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            Index { return $config.AddLogonCommand($Index,$Command,$Type,$Description) }
            Default { return $config.AddLogonCommand($Command,$Type,$Description) }
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Clear-SandboxLogonCommand
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        $config.ClearLogonCommand()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Get-SandboxLogonCommand
{
    [CmdletBinding( DefaultParameterSetName = 'All' )]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Index'
        )]
        [System.UInt16]
        $Index,

        [Parameter(
            Position = 1,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Command'
        )]
        [System.String]
        $Command,

        [Parameter(
            Position = 2,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Type'
        )]
        [SandboxCommandType]
        $Type
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            Index { return $config.GetLogonCommandByIndex($Index) }
            Command { return $config.GetLogonCommandByCommand($Command) }
            Type { return $config.GetLogonCommandByType($type) }
            Default { return $config.GetLogonCommand() }
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Remove-SandboxLogonCommand
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Index
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        if (Get-SandboxLogonCommand -Index $Index)
        {
            try
            {
                $config.RemoveLogonCommand($Index)
            }
            catch
            {
                Write-Error $_.Exception.Message
            }
        }
        else
        {
            Write-Error "Cannot find index '${Index}' because it does not exist in configuration."
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Add-SandboxMappedFolder
{
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Path,

        [Parameter(
            Position = 1,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.Boolean]
        $ReadOnly = $true,

        [Parameter(
            Position = 2,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Switch]
        $SkipPathCheck
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        # Test the path to avoid configuration errors
        if ((Test-Path -Path $Path) -or $SkipPathCheck)
        {
            try
            {
                # Add a new mapped folder in the configuration
                $config.AddMappedFolder($Path, $ReadOnly)
            }
            catch
            {
                Write-Error $_.Exception.Message
            }
        }
        else
        {
            Write-Error "Cannot find path '${Path}' because it does not exist."
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Clear-SandboxMappedFolder
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        $config.ClearMappedFolder()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Get-SandboxMappedFolder
{
    [CmdletBinding( DefaultParameterSetName = 'All' )]
    [OutputType('SandboxMappedFolder')]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Path'
        )]
        [System.String]
        $Path
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            Path { return $config.GetMappedFolder($Path) }
            Default { return $config.GetMappedFolder() }
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Remove-SandboxMappedFolder
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [System.String]
        $Path
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        if (Get-SandboxMappedFolder -Path $Path)
        {
            try
            {
                $config.RemoveMappedFolder($Path)
            }
            catch
            {
                Write-Error $_.Exception.Message
            }
        }
        else
        {
            Write-Error "Cannot find folder '${Path}' because it does not exist in configuration."
        }
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Get-SandboxNetworking
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        return $config.GetNetworking()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Set-SandboxNetworking
{
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [SandboxStatus]$Status
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        return $config.SetNetworking($Status)
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Get-SandboxVGpu
{
    [CmdletBinding()]
    Param()

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        return $config.GetVGpu()
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}

function Set-SandboxVGpu
{
    [CmdletBinding()]
    [OutputType('System.Void')]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [SandboxStatus]$Status
    )

    begin
    {
        # Show detailed information of this function
        $functionName = $MyInvocation.MyCommand.Name
        Write-Verbose "[${functionName}] Function started with Parameters: $( $PSBoundParameters | Out-String )"

        # Get the configuration instance of the Sandbox class
        $config = Get-SandboxClass -Name 'Config' -Cache
    }

    process
    {
        return $config.SetVGpu($Status)
    }

    end
    {
        Write-Verbose "[${functionName}] Complete"
    }
}