Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Adding AccessMode to PSSessionConfiguration.

### Added

- Adding LanguageMode and ExecutionPolicy to JeaSessionConfiguration.
Expand Down
15 changes: 15 additions & 0 deletions Samples/Disable Default PowerShell Session Config.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Configuration DisableDefaultPowerShell
{
Import-DscResource -Module JeaDsc

JeaSessionConfiguration DnsManagementEndpoint
{
Name = 'microsoft.powershell'
AccessMode = 'Disabled'
}
}

Remove-Item -Path C:\DscTest\* -ErrorAction SilentlyContinue
DisableDefaultPowerShell -OutputPath C:\DscTest -Verbose

Start-DscConfiguration -Path C:\DscTest -Wait -Verbose -Force
198 changes: 105 additions & 93 deletions source/Classes/1.SessionConfigurationUtility.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,146 +33,158 @@ class SessionConfigurationUtility
return $true
}

hidden [bool] TestWinRMService()
{
# Fetch the Service State
$winRMService = Get-Service -Name 'WinRM' -ErrorAction SilentlyContinue
# If the ServiceExists and the Status is running
if ($winRMService -and $winRMService.Status -eq 'Running')
{
return $true
}
else
{
return $false
}
}

## Get a PS Session Configuration based on its name
hidden [object] GetPSSessionConfiguration($Name)
{
$winRMService = Get-Service -Name 'WinRM'
if ($winRMService -and $winRMService.Status -eq 'Running')
# Ensure that the WinRMService is running.
if (-not($this.TestWinRMService()))
{
# Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
$verbosePreferenceBackup = $Global:VerbosePreference
$Global:VerbosePreference = 'SilentlyContinue'
$psSessionConfiguration = Get-PSSessionConfiguration -Name $Name -ErrorAction SilentlyContinue
$Global:VerbosePreference = $verbosePreferenceBackup
Write-Verbose -Message $script:localizedDataSession.WinRMNotRunningGetPsSession
return $null
}

if ($psSessionConfiguration)
{
return $psSessionConfiguration
}
else
{
return $null
}
# Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
$verbosePreferenceBackup = $Global:VerbosePreference
$Global:VerbosePreference = 'SilentlyContinue'
$psSessionConfiguration = Get-PSSessionConfiguration -Name $Name -ErrorAction SilentlyContinue
$Global:VerbosePreference = $verbosePreferenceBackup

if ($psSessionConfiguration)
{
return $psSessionConfiguration
}
else
{
Write-Verbose -Message $script:localizedDataSession.WinRMNotRunningGetPsSession
return $null
}

}

## Unregister a PS Session Configuration based on its name
hidden [void] UnregisterPSSessionConfiguration($Name)
{
$winRMService = Get-Service -Name 'WinRM'
if ($winRMService -and $winRMService.Status -eq 'Running')
{
# Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
$verbosePreferenceBackup = $Global:VerbosePreference
$Global:VerbosePreference = 'SilentlyContinue'
$null = Unregister-PSSessionConfiguration -Name $Name -Force -WarningAction 'SilentlyContinue'
$Global:VerbosePreference = $verbosePreferenceBackup
}
else
# Ensure that the WinRMService is running.
if (-not($this.TestWinRMService()))
{
throw ($script:localizedDataSession.WinRMNotRunningUnRegisterPsSession -f $Name)
}

# Temporary disabling Verbose as xxx-PSSessionConfiguration methods verbose messages are useless for DSC debugging
$verbosePreferenceBackup = $Global:VerbosePreference
$Global:VerbosePreference = 'SilentlyContinue'
$null = Unregister-PSSessionConfiguration -Name $Name -Force -WarningAction 'SilentlyContinue'
$Global:VerbosePreference = $verbosePreferenceBackup

}

## Register a PS Session Configuration and handle a WinRM hanging situation
hidden [Void] RegisterPSSessionConfiguration($Name, $Path, $Timeout)
hidden [Void] RegisterPSSessionConfiguration($Name, $Path, $Timeout, $AccessMode)
{
$winRMService = Get-Service -Name 'WinRM'
if ($winRMService -and $winRMService.Status -eq 'Running')
# Ensure that the WinRMService is running.
if (-not($this.TestWinRMService()))
{
Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $Name,$Path,$Timeout)
# Register-PSSessionConfiguration has been hanging because the WinRM service is stuck in Stopping state
# therefore we need to run Register-PSSessionConfiguration within a job to allow us to handle a hanging WinRM service
throw ($script:localizedDataSession.WinRMNotRunningRegisterPsSession -f $Name)
}

Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $Name,$Path,$AccessMode,$Timeout)
# Register-PSSessionConfiguration has been hanging because the WinRM service is stuck in Stopping state
# therefore we need to run Register-PSSessionConfiguration within a job to allow us to handle a hanging WinRM service

# Save the list of services sharing the same process as WinRM in case we have to restart them
$processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
$serviceList = Get-CimInstance -ClassName 'Win32_Service' -Filter "ProcessId=$processId" | Select-Object -ExpandProperty Name
foreach ($service in $serviceList.clone())
# Save the list of services sharing the same process as WinRM in case we have to restart them
$processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
$serviceList = Get-CimInstance -ClassName 'Win32_Service' -Filter "ProcessId=$processId" | Select-Object -ExpandProperty Name
foreach ($service in $serviceList.clone())
{
$dependentServiceList = Get-Service -Name $service | ForEach-Object { $_.DependentServices }
foreach ($dependentService in $dependentServiceList)
{
$dependentServiceList = Get-Service -Name $service | ForEach-Object { $_.DependentServices }
foreach ($dependentService in $dependentServiceList)
if ($dependentService.Status -eq 'Running' -and $serviceList -notcontains $dependentService.Name)
{
if ($dependentService.Status -eq 'Running' -and $serviceList -notcontains $dependentService.Name)
{
$serviceList += $dependentService.Name
}
$serviceList += $dependentService.Name
}
}
}

if ($Path)
{
$registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -Path '$Path' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
}
else
{
$registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
}
if ($Path)
{
$registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -Path '$Path' -AccessMode '$AccessMode' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
}
else
{
$registerString = "`$null = Register-PSSessionConfiguration -Name '$Name' -AccessMode '$AccessMode' -NoServiceRestart -Force -ErrorAction 'Stop' -WarningAction 'SilentlyContinue'"
}

$registerScriptBlock = [scriptblock]::Create($registerString)
$registerScriptBlock = [scriptblock]::Create($registerString)

if ($Timeout -gt 0)
if ($Timeout -gt 0)
{
$job = Start-Job -ScriptBlock $registerScriptBlock
Wait-Job -Job $job -Timeout $Timeout
Receive-Job -Job $job
Remove-Job -Job $job -Force -ErrorAction 'SilentlyContinue'

# If WinRM is still Stopping after the job has completed / exceeded $Timeout, force kill the underlying WinRM process
$winRMService = Get-Service -Name 'WinRM'
if ($winRMService -and $winRMService.Status -eq 'StopPending')
{
$job = Start-Job -ScriptBlock $registerScriptBlock
Wait-Job -Job $job -Timeout $Timeout
Receive-Job -Job $job
Remove-Job -Job $job -Force -ErrorAction 'SilentlyContinue'

# If WinRM is still Stopping after the job has completed / exceeded $Timeout, force kill the underlying WinRM process
$winRMService = Get-Service -Name 'WinRM'
if ($winRMService -and $winRMService.Status -eq 'StopPending')
$processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
Write-Verbose -Message ($script:localizedDataSession.ForcingProcessToStop -f $processId)
$failureList = @()
try
{
$processId = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name LIKE 'WinRM'" | Select-Object -ExpandProperty ProcessId
Write-Verbose -Message ($script:localizedDataSession.ForcingProcessToStop -f $processId)
$failureList = @()
try
# Kill the process hosting WinRM service
Stop-Process -Id $processId -Force
Start-Sleep -Seconds 5
Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $($serviceList -join ', '))
# Then restart all services previously identified
foreach ($service in $serviceList)
{
# Kill the process hosting WinRM service
Stop-Process -Id $processId -Force
Start-Sleep -Seconds 5
Write-Verbose -Message ($script:localizedDataSession.RegisterPSSessionConfiguration -f $($serviceList -join ', '))
# Then restart all services previously identified
foreach ($service in $serviceList)
try
{
try
{
Start-Service -Name $service
}
catch
{
$failureList += $script:localizedDataSession.FailureListStartService -f $service
}
Start-Service -Name $service
}
catch
{
$failureList += $script:localizedDataSession.FailureListStartService -f $service
}
}
catch
{
$failureList += $script:localizedDataSession.FailureListKillWinRMProcess
}

if ($failureList)
{
Write-Verbose -Message ($script:localizedDataSession.FailureListKillWinRMProcess -f $($failureList -join ', '))
}
}
elseif ($winRMService -and $winRMService.Status -eq 'Stopped')
catch
{
Write-Verbose -Message $script:localizedDataSession.RestartWinRM
Start-Service -Name 'WinRM'
$failureList += $script:localizedDataSession.FailureListKillWinRMProcess
}

if ($failureList)
{
Write-Verbose -Message ($script:localizedDataSession.FailureListKillWinRMProcess -f $($failureList -join ', '))
}
}
else
elseif ($winRMService -and $winRMService.Status -eq 'Stopped')
{
Invoke-Command -ScriptBlock $registerScriptBlock
Write-Verbose -Message $script:localizedDataSession.RestartWinRM
Start-Service -Name 'WinRM'
}
}
else
{
throw ($script:localizedDataSession.WinRMNotRunningRegisterPsSession -f $Name)
Invoke-Command -ScriptBlock $registerScriptBlock
}

}

}
48 changes: 46 additions & 2 deletions source/Classes/JeaSessionConfiguration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ class JeaSessionConfiguration:SessionConfigurationUtility
[Dscproperty()]
[string[]] $AssembliesToLoad

## Enables and disables the session configuration and determines whether it can be used for remote or local sessions on the computer.
## Values can be: Disabled, Local, Remote (Default)
[Dscproperty()]
[String] $AccessMode = 'Remote'

## The optional language mode to load
## Can be 'NoLanguage' (recommended), 'RestrictedLanguage', 'ConstrainedLanguage', or 'FullLanguage' (Default)
[Dscproperty()]
[string] $LanguageMode

Expand Down Expand Up @@ -260,6 +267,8 @@ class JeaSessionConfiguration:SessionConfigurationUtility
}
}

Write-Verbose ("Set(): AccessMode: {0}" -f $this.AccessMode)

## Register the endpoint
try
{
Expand All @@ -269,7 +278,7 @@ class JeaSessionConfiguration:SessionConfigurationUtility
$breakTheGlassName = 'Microsoft.PowerShell.Restricted'
if (-not ($this.GetPSSessionConfiguration($breakTheGlassName)))
{
$this.RegisterPSSessionConfiguration($breakTheGlassName, $null, $this.HungRegistrationTimeout)
$this.RegisterPSSessionConfiguration($breakTheGlassName, $null, $this.HungRegistrationTimeout, $this.AccessMode)
}
}

Expand All @@ -287,7 +296,7 @@ class JeaSessionConfiguration:SessionConfigurationUtility
New-PSSessionConfigurationFile @desiredState

## Register the configuration file
$this.RegisterPSSessionConfiguration($this.Name, $psscPath, $this.HungRegistrationTimeout)
$this.RegisterPSSessionConfiguration($this.Name, $psscPath, $this.HungRegistrationTimeout, $this.AccessMode)
}
}
catch
Expand Down Expand Up @@ -328,6 +337,12 @@ class JeaSessionConfiguration:SessionConfigurationUtility
return $false
}

# If the AccessMode is not within desired state.
if ($currentState.AccessMode -ne $desiredState.AccessMode)
{
return $false
}

$cmdlet = Get-Command -Name New-PSSessionConfigurationFile
$desiredState = Sync-Parameter -Command $cmdlet -Parameters $desiredState
$currentState = Sync-Parameter -Command $cmdlet -Parameters $currentState
Expand Down Expand Up @@ -357,6 +372,31 @@ class JeaSessionConfiguration:SessionConfigurationUtility
$CurrentState.Ensure = [Ensure]::Present

$sessionConfiguration = $this.GetPSSessionConfiguration($this.Name)

#
# Determine the AccessMode for the Session Configuration

if ($sessionConfiguration.Enabled -eq $false)
{
# If the Session Configuration is Disabled, then it's disabled.
$currentState.AccessMode = 'Disabled'
}
elseif (($sessionConfiguration.Permission -split ', ') -contains 'NT AUTHORITY\NETWORK AccessDenied')
{
# If the Session Configuration is Enabled and has a 'NT AUTHORITY\NETWORK AccessDenied' SDDL. Then it's local.
$currentState.AccessMode = 'Local'
}
elseif ([String]::IsNullOrEmpty($sessionConfiguration.Permission))
{
# It's not configured
$currentState.AccessMode = 'NotConfigured'
}
else
{
# If permissions are present then it's Remote.
$currentState.AccessMode = 'Remote'
}

if (-not $sessionConfiguration -or -not $sessionConfiguration.ConfigFilePath)
{
$currentState.Ensure = [Ensure]::Absent
Expand Down Expand Up @@ -404,6 +444,9 @@ class JeaSessionConfiguration:SessionConfigurationUtility
}
}

#
# PSSessionConfigurationFile Processing

# Compare current and desired state to add reasons
$valuesToCheck = $this.psobject.Properties.Name.Where({$_ -notin 'Name','Reasons'})

Expand Down Expand Up @@ -437,5 +480,6 @@ class JeaSessionConfiguration:SessionConfigurationUtility
}

return $currentState

}
}
2 changes: 1 addition & 1 deletion source/en-US/JeaSessionConfiguration.strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ConvertFrom-StringData @'
WinRMNotRunningUnRegisterPsSession = WinRM service is not running. Cannot unregister PS Session Configuration '{0}'. (JSC0004)
WinRMNotRunningRegisterPsSession = WinRM service is not running. Cannot register PS Session Configuration '{0}'. (JSC0005)
NotDefinedGMSaAndVirtualAccount = 'GroupManagedServiceAccount' and 'RunAsVirtualAccount' are not defined, setting 'RunAsVirtualAccount' to 'true'. (JSC0006)
RegisterPSSessionConfiguration = Will register PSSessionConfiguration with argument: Name = '{0}', Path = '{1}' and Timeout = '{2}' (JSC0007)
RegisterPSSessionConfiguration = Will register PSSessionConfiguration with argument: Name = '{0}', Path = '{1}', AccessMode = '{2}' and Timeout = '{3}' (JSC0007)
ForcingProcessToStop = WinRM seems hanging in Stopping state. Forcing process {0} to stop. (JSC0008)
RestartingServices = "Restarting services: {0} (JSC0009)
FailureListStartService = Start service {0} (JSC0010)
Expand Down
Loading