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" } } |