Last week the Chairman provided another prelude challenge. This challenge was intended to get you familiar with Just Enough Administration (JEA). This is an admittedly advanced topic, but the Chairman expects nothing less from his Iron Scripters. To assist you in your quest the Chairman has graciously provided another sample solution. JEA is not a cookie-cutter technology as every organization is different and every business case is slightly different.
To get started you would have needed to create a RoleCapability file and a PSSessionConfigurationFile. Your first step might have been to ask PowerShell for help.
Help New-PSRoleCapabilityFile Help New-PSSessionConfigurationFile
Given the requirements, you might have created a role capability file like this.
#BitsAdministration.psrc @{ # ID used to uniquely identify this document GUID = '2765e350-2627-46cc-8bf5-81493f357cef' # Author of this document Author = 'Art Deco' # Description of the functionality provided by these settings Description = 'A sample JEA capability file for BITS administration' # Company associated with this document CompanyName = 'Company' # Copyright statement for this document Copyright = '2019' # Modules to import when applied to a session # ModulesToImport = 'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' } ModulestoImport = "BitsTransfer" # Aliases to make visible when applied to a session #VisibleAliases = 'Item1', 'Item2' VisibleAliases = "gsv", "gcim", "dir", "h", "r" # Cmdlets to make visible when applied to a session # VisibleCmdlets = 'Invoke-Cmdlet1', @{ Name = 'Invoke-Cmdlet2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } } VisibleCmdlets = "Get-Date", "Get-History", "Invoke-History", @{ Name = 'Start-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'BITS' }, @{Name = "Passthru"}}, @{ Name = 'Stop-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'BITS' }, @{Name = "Passthru"}}, @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'BITS' }, @{Name = "Passthru"}}, @{ Name = 'Set-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'BITS' }, @{Name = 'StartupType'}, @{Name = "Passthru"}}, "bitstransfer\*" # Functions to make visible when applied to a session # VisibleFunctions = 'Invoke-Function1', # @{ Name = 'Invoke-Function2'; Parameters = @{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, @{ Name = 'Parameter2'; ValidatePattern = 'L*' } } VisibleFunctions = "help", "Get-PSSender", "Get-Service", "Get-ChildItem", "Get-CimInstance" # External commands (scripts and applications) to make visible when applied to a session # VisibleExternalCommands = 'Item1', 'Item2' VisibleExternalCommands = "c:\windows\system32\netstat.exe", "c:\windows\system32\bitsadmin.exe" # Providers to make visible when applied to a session VisibleProviders = 'FileSystem' # Scripts to run when applied to a session # ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1' # Aliases to be defined when applied to a session # AliasDefinitions = @{ Name = 'Alias1'; Value = 'Invoke-Alias1'}, @{ Name = 'Alias2'; Value = 'Invoke-Alias2'} # Functions to define when applied to a session # FunctionDefinitions = @{ Name = 'MyFunction'; ScriptBlock = { param($MyInput) $MyInput } } FunctionDefinitions = @{ Name = 'Get-PSSender'; ScriptBlock = { param() [pscustomobject]@{ ConnectionString = $PSSenderInfo.ConnectionString ConnectedUser = $PSSenderInfo.ConnectedUser RunAsUser = $PSSenderInfo.RunAsUser PSVersion = $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion } } }, @{Name = 'Get-CimInstance'; ScriptBlock = { [cmdletbinding()] [alias("gcim")] Param( [ValidateSet("win32_Service")] [string]$Classname = "Win32_Service" ) Begin { if (-Not $PSBoundParameters.ContainsKey("Classname")) { $PSBoundParameters.Add("Classname", "Win32_Service") } $PSBoundParameters.add("Filter", "Name='bits'") Write-Verbose ($PSBoundParameters | Out-String) try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('CimCmdlets\Get-CimInstance', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } #begin Process { try { $steppablePipeline.Process($_) } catch { throw } } #process End { try { $steppablePipeline.End() } catch { throw } } #end } }, @{Name = 'Get-Service'; ScriptBlock = { [CmdletBinding()] [alias("gsv")] Param() Begin { $PSBoundParameters.Add("Name", "Bits") Write-Verbose ($PSBoundParameters | Out-String) try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-Service', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } #begin Process { try { $steppablePipeline.Process($_) } catch { throw } } #process End { try { $steppablePipeline.End() } catch { throw } } #end } }, @{Name = "Get-ChildItem"; ScriptBlock = { [CmdletBinding()] [alias("dir")] Param( [Parameter(Position = 0)] [ValidateSet("C:\BitsDownloads")] [string]$Path = "C:\BitsDownloads", [Parameter(Position = 1)] [string]$Filter, [string[]]$Include, [string[]]$Exclude, [Alias('s')] [switch]$Recurse, [uint32]$Depth, [switch]$Force, [switch]$Name, [switch]$File ) Begin { try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } if (-not ($PSBoundParameters.ContainsKey("Path"))) { $PSBoundParameters.Add("Name", "C:\BitsDownloads") } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } #begin Process { try { $steppablePipeline.Process($_) } catch { throw } } #process End { try { $steppablePipeline.End() } catch { throw } } #end } } # Variables to define when applied to a session # VariableDefinitions = @{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, @{ Name = 'Variable2'; Value = 'StaticInitialValue' } # Environment variables to define when applied to a session # EnvironmentVariables = @{ Variable1 = 'Value1'; Variable2 = 'Value2' } # Type files (.ps1xml) to load when applied to a session # TypesToProcess = 'C:\ConfigData\MyTypes.ps1xml', 'C:\ConfigData\OtherTypes.ps1xml' # Format files (.ps1xml) to load when applied to a session # FormatsToProcess = 'C:\ConfigData\MyFormats.ps1xml', 'C:\ConfigData\OtherFormats.ps1xml' # Assemblies to load when applied to a session # AssembliesToLoad = 'System.Web', 'System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' }
This file would most likely be placed in a RoleCapabilities folder in a module that you will deploy to the target server.
The psm1 file can be empty with no functions exported. The proxy and helper functions are defined in the psrc file but they could also have been defined in the module. Otherwise, the manifest is pretty simple.
# # Module manifest for module 'BitsAdmin' # @{ # Script module or binary module file associated with this manifest. RootModule = 'bitsadmin.psm1' # Version number of this module. ModuleVersion = '1.2.0' # Supported PSEditions CompatiblePSEditions = @("Desktop") # ID used to uniquely identify this module GUID = 'd3c7dad3-6ade-40c5-9f30-e41bdd23d1e0' # Author of this module Author = 'The Chairman' # Company or vendor of this module CompanyName = '' # Copyright statement for this module Copyright = '' # Description of the functionality provided by this module # Description = '' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '5.1' # Name of the Windows PowerShell host required by this module # PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # DotNetFrameworkVersion = '' # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # CLRVersion = '' # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @('bitstransfer') # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module # FormatsToProcess = @() # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = '' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = '*' # DSC resources to export from this module # DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. # Tags = @() # A URL to the license for this module. # LicenseUri = '' # A URL to the main website for this project. # ProjectUri = '' # A URL to an icon representing this module. # IconUri = '' # ReleaseNotes of this module # ReleaseNotes = '' } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' }
To use this will require a PSSessionConfiguration file.
$params = @{ Path = ".\myBits.pssc" SessionType = "RestrictedRemoteServer" TranscriptDirectory = "c:\JEA-Transcripts" RunAsVirtualAccount = $True Description = "Company BITS Admin endpoint" RoleDefinitions = @{'Company\BitsAdmins' = @{ RoleCapabilities = 'BITSAdministration' }} } New-PSSessionConfigurationFile @params
Here is the completed file.
#myBits.pssc @{ # Version number of the schema used for this document SchemaVersion = '2.0.0.1' # ID used to uniquely identify this document GUID = 'c09ce4c6-3759-472c-b48c-7a2b5e4c6419' # Author of this document Author = 'Art Deco' # Description of the functionality provided by these settings Description = 'Company BITS Admin endpoint' # Session type defaults to apply for this session configuration. Can be 'RestrictedRemoteServer' (recommended), 'Empty', or 'Default' SessionType = 'RestrictedRemoteServer' # Directory to place session transcripts for this session configuration TranscriptDirectory = 'C:\JEA-Transcripts' # Whether to run this session configuration as the machine's (virtual) administrator account RunAsVirtualAccount = $true # Scripts to run when applied to a session #ScriptsToProcess = 'C:\ConfigData\InitScript2.ps1' # User roles (security groups), and the role capabilities that should be applied to them when applied to a session RoleDefinitions = @{ 'BitsAdmins' = @{ 'RoleCapabilities' = 'BITSAdministration' } } }
It is a good idea to test it.
Test-PSSessionConfigurationFile .\myBits.pssc
At this point you will want to set up the Active Directory domain with the necessary groups and user accounts.
#domain admin credential $cred = Get-Credential Company\artd $dc = New-PSSession -VMName DOM1 -Credential $cred #create a global group Invoke-Command { New-ADGroup -Name BitsAdmins -GroupScope Global } -Session $dc #create a test user account Invoke-Command { $p = @{ Name = "BillBits" SamAccountName = "billb" UserPrincipalName = "[email protected]" AccountPassword = (ConvertTo-SecureString "[email protected]" -AsPlainText -force) Enabled = $True passthru = $True } New-ADUser @p } -session $dc #add the user to the BitsAdmin domain global group Invoke-Command { Add-ADGroupMember -Identity "BitsAdmins" -Members (Get-ADUser billb) } -session $dc Remove-PSSession $dc
Next, the node needs to be setup.
$s = New-PSSession -VMName SRV1 -Credential $cred #add the Bits feature Invoke-Command { Add-WindowsFeature Bits } -session $s #copy the module assuming in the parent location $copyparams = @{ Path = ".\BitsAdmin" Container = $True Recurse = $True Destination = "$env:ProgramFiles\WindowsPowerShell\Modules" ToSession = $s force = $True } Copy-item @copyparams #verify the module Invoke-Command { Get-Module BitsAdmin -list } -session $s #create Transcript folder Invoke-Command { If (-Not (Test-Path c:\JEA-Transcripts)) { New-Item C:\JEA-Transcripts -ItemType Directory } } -session $s #create BitsTransfer folder Invoke-Command { If (-Not (Test-Path C:\BitsDownloads)) { New-item C:\BitsDownloads -ItemType Directory } Set-Content -Path C:\BitsDownloads\readme.txt -Value "This is a sample file." } -session $s #copy the pssc Copy-Item -Path .\myBits.pssc -Destination C:\ -ToSession $s -force #setup the new one Invoke-Command { Register-PSSessionConfiguration -Path C:\myBits.pssc -Name BitsAdmin} -session $s #Get session config to verify Invoke-Command {Get-PSSessionConfiguration bitsadmin | Select-Object *} -session $s #need an execution policy so modules will load Invoke-Command { Set-ExecutionPolicy remotesigned -force } -session $s
Once setup you might want to verify what the user can and cannot do.
Invoke-Command { Get-PSSessionCapability -ConfigurationName BitsAdmin -Username company\billb } -session $s #test as user $bill = Get-Credential Company\billb $test = New-PSSession -VMName SRV1 -Credential $bill -ConfigurationName bitsadmin Enter-PSSession $Test #run commands and verify what user can and cannot do
Creating and deploying a JEA configuration takes some planning, testing and refinement.
The Chairman will be back with another prelude challenge.
One Reply to “Iron Scripter Prelude 3 Solution”
Comments are closed.