DSCResources/MSFT_xJeaEndpoint/MSFT_xJeaEndpoint.psm1
#region Helper # Internal function to throw terminating error with specified errroCategory, errorId and errorMessage function New-TerminatingError { param ( [Parameter(Mandatory)] [String]$errorId, [Parameter(Mandatory)] [String]$errorMessage, [Parameter(Mandatory)] [System.Management.Automation.ErrorCategory]$errorCategory ) $exception = New-Object System.InvalidOperationException $errorMessage $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null throw $errorRecord } function Get-JeaDir { Join-Path $env:ProgramFiles 'Jea' } function Get-JeaToolKitDir { Join-Path (Get-JeaDir) 'Toolkit' } function Get-JeaUtilDir { Join-Path (Get-JeaDir) 'Util' } function Get-JeaStartupScriptDir { Join-Path (Get-JeaDir) 'StartupScript' } function Get-JeaActivityDir { Join-Path (Get-JeaDir) 'Activity' } <# .Synopsis Creates a random password. .DESCRIPTION Creates a random password by generating a array of characters and passing it to Get-Random .EXAMPLE PS> New-RandomPassword g0dIDojsRGcV .EXAMPLE PS> New-RandomPassword -Length 3 dyN .EXAMPLE PS> New-RandomPassword -Length 30 -UseSpecialCharacters r5Lhs1K9n*joZl$u^NDO&TkWvPCf2c #> function New-RandomPassword { [CmdletBinding()] [OutputType([String])] Param ( # Length of the password [Parameter(Mandatory=$False, Position=0)] $Length=12, # Includes the characters !@#$%^&*-+ in the password [switch]$UseSpecialCharacters ) [char[]]$allowedChars = ([Char]'a'..[char]'z') + ([char]'A'..[char]'Z') + ([byte][char]'0'..[byte][char]'9') if ($UseSpecialCharacters) { foreach ($c in '!','@','#','$','%','^','&','*','-','+') { $allowedChars += [char]$c } } return (( $allowedChars |Get-Random -Count $length | % {[char]$_}) -join '') } Set-Alias New-RandomString New-RandomPassword <# .Synopsis Create a User Account with a random name .DESCRIPTION .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet #> function New-JeaSessionAccount { [CmdletBinding(DefaultParameterSetName='UserNamePrefix')] Param ( #******************************************************************************** # A random username is generated. This parameter gives you the opportunity to # to prefix that name with a string to help identify the account [Parameter(Mandatory=$false, ParameterSetName='UserNamePrefix')] [ValidateLength(0,12)] $UserNamePrefix = 'JSA-', [Parameter(Mandatory=$true, ParameterSetName='UserName')] $UserName, [string] $ComputerName = $Env:COMPUTERNAME, [string] $Description = "JEA Service Account created by $($env:UserDomain + '\' + $ENV:Username) at $(get-date)", [string[]] $Group='Administrators' ) if (!($PSCmdlet.ParameterSetName -eq 'UserName')) { $MaxWindowsUserLength = 20 $Templength = $MaxWindowsUserLength - $UserNamePrefix.Length $UserName = $UserNamePrefix + (New-RandomString -Length $TempLength) } $Computer = [ADSI]"WinNT://$COMPUTERNAME,Computer" Write-Verbose "New [JeaSessionAccount]$UserName" $User = $Computer.Create('User', $UserName) $password = New-RandomPassword -Length 127 -UseSpecialCharacters $cred = new-object pscredential $username, (convertto-securestring $password -asplaintext -force) $null = $User.SetPassword($password) $null = $User.SetInfo() $null = $User.FullName = 'Jea Session Account' $null = $User.SetInfo() $null = $User.Description = $Description $null = $User.SetInfo() $User.UserFlags = 64 # ADS_UF_PASSWD_CANT_CHANGE http://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx $null = $User.SetInfo() foreach ($item in $group) { Write-Verbose " [JeaSessionAccount]$UserName ADD [Group]$item" $gobj = [ADSI]"WinNT://$COMPUTERNAME/$item,group" $null = $gobj.add("WinNT://$ComputerName/$UserName") } return $cred } function Test-JeaSessionAccount { [CmdletBinding()] [OutputType([Boolean])] Param ( [Parameter(Mandatory=$true, Position=0)] $UserName, $ComputerName = $Env:COMPUTERNAME ) $oldDebugPrefernce = $DebugPreference; $oldVerbosePreference = $VerbosePreference $DebugPreference = $VerbosePreference = 'SilentlyContinue' $user = Get-CimInstance -Query "select * from Win32_UserAccount Where Name=""$UserName"" AND LocalAccount=""TRUE""" -ComputerName $ComputerName -Verbose:0 $DebugPreference = $oldDebugPrefernce; $VerbosePreference = $oldVerbosePreference return [Boolean]$user } function Reset-JeaSessionAccountPassword { [CmdletBinding()] Param ( #******************************************************************************** # A random username is generated. This parameter gives you the opportunity to # to prefix that name with a string to help identify the account [Parameter(Mandatory=$true, Position=0)] $UserName, $ComputerName = $Env:COMPUTERNAME ) $user = [ADSI]"WinNT://$computerName/$username,user" $password = New-RandomPassword -Length 127 -UseSpecialCharacters $null = $user.setpassword($password) $null = $user.SetInfo() $cred = new-object pscredential $username, (convertto-securestring $password -asplaintext -force) return $cred } function New-InitializationFile { [CmdletBinding()] [Alias()] [OutputType([int])] Param ( [Parameter(Mandatory=$true, Position=0)] $Path, [Parameter(Position=1)] $Toolkit ) Write-Verbose "New [InitializationFile]$path" ""> $path $toolkitNames = @() foreach ($t in $toolkit) { $toolkitNames += ("$t" +'-Toolkit') } @" <# This is a auto-generated JEA startup file Generated At: $(Get-date) Generated On: $(hostname) Generated By: $($env:UserDomain + '\' + $env:UserName) #> function whoami {`$PSSenderInfo} set-alias exit exit-PSSessions `$Init = (Join-Path `$env:ProgramFiles 'Jea\Util\Initialize-Toolkit.ps1') . `$Init -toolkit $($Toolkitnames -join ',') "@ > $path <# @" `$motd = (Join-Path `$env:ProgramFiles 'Jea\motd.txt') if (test-path `$motd) { Write-Host (cat `$motd) } `$toolkitNames = @() `$Applications = @() $( foreach ($Name in $toolkitNames) { @" Import-Module `$(Join-Path (Join-Path `$env:ProgramFiles 'Jea\Toolkit') ('$name' + '.psm1' )) -Force -DisableNameChecking; `$toolkitNames += '$name'; `$module = Get-Module $name `$Applications += &`$module{`$ExportedApplications} "@ } ) "@ > $path @" `$PSModuleAutoloadingPreference = 'none'; `$allowed = ` 'Exit-PSSession','exit' ,'Get-Command' ,'Get-FormatData' ,'Get-Help','help','man' ,'Measure-Object','measure' ,'Out-Default' ,'get-module','gmo' ,'format-list','format-table', 'Sort-Object','Where-object','Select-Object' ,'get-alias' ,whoami Get-Command |where {`$_.Name -notin `$allowed} | foreach { if (`$_.ModuleName -notin `$toolKitNames) {`$_.Visibility='Private'} } Get-Module -Name `$toolKitNames | foreach {`$_.LogPipelineExecutionDetails = `$true} #Certain commands are particularly dangerous so make sure that they are not exposed. foreach (`$c in 'Invoke-Expression','New-Object') { (Get-Command `$c).Visibility = 'Private' } <# `$ExecutionContext.SessionState.Applications.Clear() foreach (`$a in `$applications) { try { `$path = (Get-Command -CommandType Application -Name `$a).Source `$ExecutionContext.SessionState.Applications.Add(`$path) }catch { } } `$ExecutionContext.SessionState.Scripts.Clear() `$ExecutionContext.SessionState.LanguageMode='Nolanguage' "@ >> $path #> } $ResetScript = { #This resets the passwords for all the RUnas [char[]]$pwChars = ([Char]'a'..[char]'z') + ([char]'A'..[char]'Z') + ([byte][char]'0'..[byte][char]'9') foreach ($c in '!','@','#','$','%','^','&','*','-','+') { $pwChars += [char]$c } $endpoint = Get-PSSessionConfiguration |where {$_.RunAsUser -like 'JSA-*'} if ($endpoint) { foreach ($ep in $endpoint) { $user = [ADSI]"WinNT://$($env:computerName)/$($ep.RunAsUser),user" $password = (( $pwChars |Get-Random -Count 127 | % {[char]$_}) -join '') $null = $user.setpassword($password) $null = $user.SetInfo() $cred = new-object pscredential ($ep.RunAsUser), (convertto-securestring $password -asplaintext -force) Set-PSSessionConfiguration -Name $($ep.Name) -RunAsCredential $cred -Force } Restart-Service Winrm -Force } } function Assert-JeaAdmin { try { if (!(Test-JeaSessionAccount -UserName JeaSchTaskAccount)) { Write-Verbose 'New [JeaScheduledTaskAccount]' $cred = New-JeaSessionAccount -UserName JeaSchTaskAccount -Group Administrators -Description "This is a special Jea account to run the ResetJeaSessionAccountPasswords Scheduled task" } $UtilDir = Get-JeaUtilDir if (!(Test-Path $UtilDir)) { Write-Verbose "New [JeaDirectory]$utildir" mkdir -Force -Path $UtilDir } $ResetPS1 = Join-Path $UtilDir 'ScheduledPasswordReset.ps1' if (!(TESt-Path $ResetPs1) -or (cat $resetPS1 -Delimiter "None") -ne $resetscript) { Write-Verbose 'Reset [JeaScheduledPasswordResetScript]' $resetScript > $ResetPS1 } $wd = "`"$UtilDir`"" $file = "`"$ResetPS1`"" $Arguments = "-nologo -executionPolicy ByPass -NoProfile -NonInteractive -file $file -WorkingDirectory $wd -WindowStyle hidden" $s = Get-ScheduledTask -TaskPath \Microsoft\Windows\Jea\ -TaskName ResetJeaSessionAccountPasswords -ErrorAction SilentlyContinue Write-Verbose "Test [JEAScheduledTask]ResetJeaSessionAccountPasswords $([Bool]$s)" if (!($s) -or $s.Actions.Count -ne 1 -or $s.Actions[0].Execute -ne 'PowerShell.exe' -or $s.Actions[0].Arguments -ne $Arguments) { $action = New-ScheduledTaskAction -Execute PowerShell.exe -Argument $arguments $trigger = New-ScheduledTaskTrigger -Daily -At 1:00 Write-Verbose 'Register [JEAScheduledTask]ResetJeaSessionAccountPasswords' if ($Cred) { $cred = Reset-JeaSessionAccountPassword -UserName JeaSchTaskAccount } Register-ScheduledTask -TaskName ResetJeaSessionAccountPasswords -Action $action ` -TaskPath \Microsoft\Windows\Jea -Description 'Reset Jea Session Account Passwords' ` -Trigger $trigger ` -User $cred.UserName -Password $cred.GetNetworkCredential().Password } }catch { write-verbose "ERROR: $($_|fl * -force|out-string)" throw } } #endregion function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String] $Name ) try { $endpoint = Get-PSSessionConfiguration -Name $Name -ErrorAction SilentlyContinue -Verbose:0 if ($endpoint) { $returnValue = @{ Name = [System.String]$Name Toolkit = [System.String[]]"TODO: GET TOOLKITS" Ensure = [System.String]'Present' SecurityDescriptorSddl = [System.String]$endpoint.SecurityDescriptorSddl Group = [String[]]$( "TODO: Get Groups" ) } } else { $returnValue = @{ Name = [System.String]$Name Ensure = [System.String]'Absent' } } $returnValue }catch { write-Debug "ERROR: $($_|fl * -force|out-string)" New-TerminatingError -errorId 'SetJeaEndpointFailed' -errorMessage $_.Exception -errorCategory InvalidOperation } } function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String] $Name, [System.String[]] $Toolkit, [System.String] $SecurityDescriptorSddl='O:NSG:BAD:P(A;;GA;;;BA)(A;;GA;;;RM)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)', [System.String[]] $Group = @('Administrators'), [System.Boolean] $CleanAll, [ValidateSet('Present','Absent')] [System.String] $Ensure = 'Present' ) try { if ($CleanAll) { #region Remove non-default endpoints Write-Verbose 'Remove [JeaEndpoints]*' $defaultEndpoints = 'microsoft.powershell','microsoft.powershell.workflow','microsoft.powershell32','microsoft.windows.servermanagerworkflows' Get-PSSessionConfiguration -Verbose:0 |where Name -NotIn $defaultEndpoints | % { Write-Verbose "Remove [PSEndpoint]$($_.Name)" Unregister-PSSessionConfiguration -NoServiceRestart -Force -Verbose:0 -Name $_.Name } #endregion #region Remove the JSA UserAccounts Write-Verbose 'Remove [JSAUserAccount]*' [ADSI]$Server="WinNT://$($Env:COMPUTERNAME)" foreach ($account in (Get-CimInstance -Query 'select * from Win32_UserAccount Where Name Like "JSA-%" AND LocalAccount="TRUE"').Name) { Write-Verbose "Remove [JSAUserAccount]$account" $server.Delete('user',$account) } #endregion #region remove StartupFiles if (test-path (Get-JeaStartupScriptDir)) { Write-Verbose 'Remove [JeaStartupfile]*' Remove-Item (Join-Path (Get-JeaStartupScriptDir) *) -Recurse -Verbose } #endregion } else { Assert-JeaAdmin $endPointName = $Name $endPoint = Get-PSSessionConfiguration -Name $Name -Verbose:0 -ErrorAction SilentlyContinue $account = "JSA-$Name" if ($Ensure -AND !($endPoint)) { #region ACCOUNT Write-Verbose "Test [JeaSessionAccount]$account" if (Test-JeaSessionAccount -UserName $account) { Write-Verbose " [JeaSessionAccount]$account = `$true; Reset-JeaSessionAccountPassword" $cred = Reset-JeaSessionAccountPassword -UserName $account }else { Write-Verbose "New [JeaSessionAccount]$account" $cred = New-JeaSessionAccount -UserName $account -Description 'PowerShell Session Acount' -Group $group } #endregion #region InitializationFile $startupfile = Join-Path (Get-JeaStartupScriptDir) "Initialize-$($Name).ps1" Write-Verbose "New [JeaStartupFile]$StartupFile -> [Jeatoolkit]$Toolkit" New-InitializationFile -Path $StartupFile -Toolkit $Toolkit #endregion #region EndPoint Write-Verbose "New [JeaEndPoint]$Name" try { # Set the following preference so the functions inside Unregister-PSSessionConfig doesn't get these settings $oldDebugPrefernce = $DebugPreference; $oldVerbosePreference = $VerbosePreference $DebugPreference = $VerbosePreference = 'SilentlyContinue' $null = Register-PSSessionConfiguration -Name $Name -StartupScript $startupfile -RunAsCredential $cred -NoServiceRestart -Verbose:0 -Force -SecurityDescriptorSddl $SecurityDescriptorSddl # Reset the following preference to older values $DebugPreference = $oldDebugPrefernce; $VerbosePreference = $oldVerbosePreference Write-Verbose ' TODO - Figure out how to restart WINRM here' } catch { write-Debug "ERROR: $($_|fl * -force|out-string)" New-TerminatingError -errorId 'JeaEndpointConfigurationFailed' -errorMessage $_.Exception -errorCategory InvalidOperation } #endregion }elseif (!($Ensure) -And $EndPoint) { Write-Verbose "Unregister [JeaEndpoint]$Name" Unregister-PSSessionConfiguration -Name $Name -Force -Verbose:0 if (Test-JeaSessionAccount -UserName $account) { [ADSI]$Server="WinNT://$($Env:COMPUTERNAME)" $server.Delete('user',$account) } } } }catch { write-Debug "ERROR: $($_|fl * -force|out-string)" New-TerminatingError -errorId 'SetJeaEndpointFailed' -errorMessage $_.Exception -errorCategory InvalidOperation } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String] $Name, [System.String[]] $Toolkit, [System.String] $SecurityDescriptorSddl, [System.String[]] $Group, [System.Boolean] $CleanAll, [ValidateSet('Present','Absent')] [System.String] $Ensure = 'Present' ) try { if ($Cleanall) { return $false }else { Write-Verbose "Test [JeaEndPoint]$name" $endPoint = Get-PSSessionConfiguration -Name $Name -Verbose:0 -ErrorAction Stop $namePresentMessage = " [JeaEndPoint]$name Present" Write-Verbose -Message $namePresentMessage if ($Ensure -eq 'Absent') { return $false } else { Write-Verbose ' TODO: Check for Toolkits, StartupScript and UserAccount' return $true } } }catch [Microsoft.PowerShell.Commands.WriteErrorException] { Write-Verbose " [JeaEndPoint]$Name Absent" return ($Ensure -eq 'Absent') } } Export-ModuleMember -Function *-TargetResource |