Hardening PowerShell — Logging, AMSI, Language Modes, and JEA
· Go Komura · PowerShell, Security, Windows, Logging, Auditing, Corporate IT, Privilege Management, Operational Improvement
After a run of reports that “PowerShell was abused during the intrusion,” some organisations try to ban the use of PowerShell entirely across the company. But that does not pay off, either in effectiveness or in business impact. PowerShell is the management foundation of Windows itself, and stopping it stops your operational automation. Meanwhile an attacker can achieve the equivalent by other means.
The realistic policy is not prohibition, but visibility and restriction. Fortunately, PowerShell 5.0 and later ships with features for the defending side: script block logging, which records even obfuscated code in its expanded form; AMSI, which hands the content to your antivirus product before execution; language modes, which restrict the very syntax that can be run; and JEA, which delegates “only the operations that are needed.” Combine them and you can reach an auditable state without stopping the business.
This article summarises the settings you should apply in order to keep using PowerShell safely in an internal Windows environment, in order of effectiveness. Execution policy and signing are covered in “PowerShell Execution Policy and Script Signing”, so this article picks up from there.
1. The Bottom Line First
- The top priority is enabling script block logging. Executed code is recorded in the event log, and obfuscated code is retained in its expanded form (event ID 4104).1
- Enable transcription alongside it. Session records including input and output can be aggregated to a write-only share.1
- Once logging is enabled, review your log size settings too. Left at the defaults, they will wrap in a short period.1
- AMSI hands the script to your antivirus product before execution. It is active on PowerShell 5.0 and later, on Windows 10 and later.2
- Disable the old PowerShell 2.0 engine. While it remains, there is room to switch to an old engine where neither logging nor AMSI applies.3
- The execution policy is not a security boundary. Microsoft states this explicitly. Do not make it the core of your defence.4
- Use language modes as a consequence of WDAC/AppLocker. Setting them manually is bypassable and does not function as a security feature.5
- Use JEA for delegating privileges. Permit “only this parameter, of this command,” and keep operations running without handing out administrator rights.6
2. What Are You Protecting? — Visibility First, Restriction Second
Before listing measures, decide on priorities.
| Stage | What to do | Effect |
|---|---|---|
| 1. Visibility | Script block logging, transcription | You can tell what happened. After-the-fact investigation becomes possible |
| 2. Basic neutralisation | Disable PowerShell 2.0, keep up to date, verify AMSI | Close routes that bypass inspection |
| 3. Limiting privileges | Delegation via JEA, reducing administrator rights | Limit the blast radius |
| 4. Restricting execution | WDAC/AppLocker + constrained language mode | Stop unapproved code from running at all |
In many organisations, doing just 1 and 3 improves the situation dramatically. Item 4 has a high deployment cost, so it is an area to advance while verifying the impact on the business.
3. Enable Logging — 4104 Is the Most Important
PowerShell logging has three layers.1
| Type | What is recorded | Event ID |
|---|---|---|
| Module logging | Details of pipeline execution for specified modules | 4103 |
| Script block logging | The text of the code that was executed (after de-obfuscation) | 4104 |
| Destination log | 5.1 writes to Microsoft-Windows-PowerShell/Operational, 7 to PowerShellCore/Operational |
— |
| Transcription | Records session input and output to a text file | — (file output) |
All of these can be configured under Group Policy at “Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell”. You can also set them directly in the registry.1
# Enable script block logging (requires administrator rights; normally deployed via GPO)
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
New-Item -Path $key -Force | Out-Null
# -Type is a dynamic parameter added by the registry provider. It lets you state the value type (DWord) explicitly
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 1 -Type DWord
# PowerShell 7 (pwsh) looks at a different policy key. Set both
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 1 -Type DWord
# Enable transcription and aggregate it to a write-only share
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableTranscripting' -Value 1 -Type DWord
Set-ItemProperty -Path $key -Name 'OutputDirectory' -Value '\\logsrv\pstranscripts$'
Set-ItemProperty -Path $key -Name 'EnableInvocationHeader' -Value 1 -Type DWord
# Transcription also has a separate key on the PowerShell 7 side. Write the same values
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\Transcription'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableTranscripting' -Value 1 -Type DWord
Set-ItemProperty -Path $key -Name 'OutputDirectory' -Value '\\logsrv\pstranscripts$'
Set-ItemProperty -Path $key -Name 'EnableInvocationHeader' -Value 1 -Type DWord
PowerShell 7 reads policy under PowerShellCore, not under Windows\PowerShell. If you configure only the 5.1 side and call it done, records of scripts run under pwsh are missing entirely. For both script block logging and transcription, write the same values to both keys (if you deploy via GPO, configure each in its respective template).
The value of script block logging lies in its resistance to obfuscation. Whether the command was Base64-encoded or assembled by string concatenation, what is recorded is the content as expanded at execution time.1 Whether you can reconstruct “what was executed” during an incident investigation is decided by whether this setting is in place.
To check the records, use Get-WinEvent (see “Investigating Event Logs Practically With Get-WinEvent”).
# Check script block logs from the last day.
# Windows PowerShell (5.1) and PowerShell 7 write to different logs,
# so cover both
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-PowerShell/Operational', 'PowerShellCore/Operational'
ID = 4104
StartTime = (Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue |
Select-Object TimeCreated, LogName, @{ n='Script'; e={ $_.Message } } -First 20
Once you have enabled it, be sure to review the log size. At the default size it will cycle through within hours to days, and nothing will be left when you need it.
Get-WinEvent -ListLog 'Microsoft-Windows-PowerShell/Operational', 'PowerShellCore/Operational' |
Select-Object LogName, MaximumSizeInBytes, RecordCount
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824 # example: expand to 1 GB
wevtutil sl PowerShellCore/Operational /ms:1073741824 # don't forget the PowerShell 7 side
The key point for the transcript output location is that it should be a share users can only write to, and cannot delete from or overwrite. Keep it locally and a compromised machine simply has it deleted.
4. Close the Bypass Routes — PowerShell 2.0 and AMSI
Through AMSI (Antimalware Scan Interface), PowerShell 5.0 and later hands the content of a script to the antivirus product immediately before execution, so it can be inspected in de-obfuscated form.2 If you use a product that supports it, including Microsoft Defender, it works with no additional configuration.
The problem is that the old engine has no such mechanism. If the Windows PowerShell 2.0 engine is left enabled, powershell.exe -Version 2 still leaves room to switch to an environment where neither logging nor AMSI applies. This feature is deprecated and disabling it is recommended.3
# Check the state of the PowerShell 2.0 engine and disable it (requires administrator rights; a restart may be needed)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2* |
Select-Object FeatureName, State
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
Note that if you have deployed PowerShell 7 (pwsh), both the configuration keys and the log destination differ from 5.1 (policy lives under PowerShellCore, logs go to PowerShellCore/Operational). In an environment where both are used, apply configuration, log sizing, and investigation queries to both. For coexistence of versions, see “The Differences Between Windows PowerShell 5.1 and PowerShell 7”.
5. Don’t Misjudge Where the Execution Policy Fits
Let us be explicit once more: the execution policy is not a security boundary. The official documentation states that it is a safety feature to prevent users from unintentionally running a script, not something that prevents malicious action.4
That said, it is not worthless. Signing has a separate value: “you can confirm that a distributed module has not been tampered with” (see “Distributing and Updating PowerShell Modules Internally”). What matters is understanding its role correctly and using it accordingly; the most dangerous belief is “we set AllSigned, so we’re safe.”
6. Language Modes — Use Them Together With Application Control
A PowerShell session has a language mode, which restricts the language elements available.5
| Mode | What you can use |
|---|---|
| FullLanguage | Everything (default) |
| ConstrainedLanguage | Only approved types. Calls to arbitrary .NET types and the like are restricted |
| RestrictedLanguage | Commands can be run, but script blocks cannot |
| NoLanguage | Command execution only (used from APIs) |
You can check the current mode as follows.
$ExecutionContext.SessionState.LanguageMode
What matters is how it is set. Constrained language mode works by PowerShell switching to it automatically when you have configured allow-list-based application control with WDAC (Windows Defender Application Control) or AppLocker.5 Setting it manually through environment variables and the like is easy to bypass and does not function as a security feature. Understand that narrowing only the language mode without deploying application control gives you little in return for the effort.
7. JEA — Delegating “Only the Operations That Are Needed”
What determines the real-world damage is, in most cases, the breadth of privileges. Where “the help desk has administrator rights” or “everyone in operations is in Domain Admins,” the compromise of one machine becomes the compromise of the whole company.
JEA (Just Enough Administration) is a mechanism for delegating specific operations without handing over administrator privileges.6 It fits requirements such as “let the help desk restart the application service, and nothing more” exactly.
Step 1: Define the permitted operations in a role capability file (.psrc)
A role capability file must be placed in the RoleCapabilities folder of a PowerShell module. Merely creating the folder does not make it recognised as a module, and the name will not resolve from RoleDefinitions described later. So first create the containing module (a folder with a manifest).6
# Create the module that will hold the role capability (a manifest is required)
$moduleRoot = 'C:\Program Files\WindowsPowerShell\Modules\KsJea'
$null = New-Item -Path "$moduleRoot\RoleCapabilities" -ItemType Directory -Force
# A module folder needs at least one file with the same name as the folder
$null = New-Item -Path "$moduleRoot\KsJea.psm1" -ItemType File -Force
New-ModuleManifest -Path "$moduleRoot\KsJea.psd1" -RootModule 'KsJea.psm1'
# Create a skeleton role capability file (the file name becomes the role name).
# Role names are resolved by name alone across every module on PSModulePath,
# so a generic name like 'HelpDesk' can collide with another module's .psrc.
# On collision there is no guarantee which one is chosen, and unintended privileges may be granted.
# Use a unique name with your organisation's prefix
New-PSRoleCapabilityFile -Path "$moduleRoot\RoleCapabilities\KsHelpDesk.psrc"
# Verify that it is visible as a module
Get-Module -Name KsJea -ListAvailable
# KsHelpDesk.psrc (excerpt) — declare what is permitted, and how far
@{
GUID = '....'
# Read-only commands can be exposed as they are
VisibleCmdlets = @(
'Get-Service',
'Get-EventLog'
)
# Restart-Service changes state, so deliberately keep it out of VisibleCmdlets (see below)
# VisibleFunctions only narrows down "functions already loaded into the session";
# it does not define functions. For your own functions, write the body in
# FunctionDefinitions and then also list the name in VisibleFunctions (both are needed)
VisibleFunctions = @('Get-KsAppStatus', 'Restart-KsAppService')
FunctionDefinitions = @(
@{
Name = 'Get-KsAppStatus'
ScriptBlock = {
# The body of a function runs in the default language mode, so it is not subject to JEA's restrictions.
# Never pass user input straight through to a dangerous command
Get-Service -Name 'KsAppService' |
Microsoft.PowerShell.Utility\Select-Object Name, Status, StartType
}
},
@{
# Expose the restart as a function that can only receive its target via an argument
Name = 'Restart-KsAppService'
ScriptBlock = {
param(
[Parameter(Mandatory)]
[ValidateSet('KsAppService', 'Spooler')]
[string] $Name
)
Microsoft.PowerShell.Management\Restart-Service -Name $Name
}
}
)
VisibleExternalCommands = @()
}
ValidateSet on VisibleCmdlets alone will not safely constrain a state-changing command. Restrictions expressed with Parameters and ValidateSet are only evaluated when that parameter is actually bound. Restart-Service can receive a ServiceController from the pipeline, so if someone writes
Get-Service WinRM | Restart-Service
then -Name is never bound and ValidateSet is bypassed entirely. On an endpoint you believed could “only restart KsAppService and Spooler,” any service can in fact be restarted.
The same thing happens with a different parameter set. Even if you attach a ValidateSet for LogName on Get-WinEvent, writing
Get-WinEvent -ProviderName Microsoft-Windows-Security-Auditing -MaxEvents 1
leaves LogName unbound and the restriction inert. Because a JEA session runs as a virtual administrator, in this case even the Security log becomes readable.
That is why the example above keeps Restart-Service out of VisibleCmdlets and exposes only a wrapper function that can receive its target through an argument (Restart-KsAppService). If the only input path is an argument, there is no way to work around it.
Restrictions using Parameters and ValidateSet only take effect “when that parameter is bound.” For commands where binding can be avoided via pipeline input or a different parameter set, the restriction itself is void. Take it as a rule that commands whose arguments you want to constrain should be wrapped in a wrapper function.
There are two behaviours here that are easy to trip over. The first is that VisibleFunctions does not create functions. A function whose name is merely listed does not exist in the session, and users see “no such command.” Define your own functions in FunctionDefinitions and then also list them in VisibleFunctions.7 If the number grows, it is easier to manage by splitting them out into a script module and exposing that module’s functions through VisibleFunctions.
The second is that the body of a function is not subject to JEA’s restrictions.7 If you want to use a constrained command that JEA substitutes, such as Select-Object, with its original behaviour, call it fully qualified as Microsoft.PowerShell.Utility\Select-Object, as above. Conversely, this means anything is possible inside a function, so absolutely avoid writing code that passes user input into Invoke-Expression.
Step 2: Define who gets which role in a session configuration file (.pssc)
# SessionType: restricted remote server (NoLanguage by default)
# RunAsVirtualAccount: run as a virtual administrator account
# TranscriptDirectory: record what is executed
$pssc = @{
Path = '.\KsHelpDesk.pssc'
SessionType = 'RestrictedRemoteServer'
RunAsVirtualAccount = $true
TranscriptDirectory = 'C:\JeaTranscripts'
RoleDefinitions = @{ 'EXAMPLE\HelpDesk' = @{ RoleCapabilities = 'KsHelpDesk' } }
}
New-PSSessionConfigurationFile @pssc
Step 3: Register it
Register-PSSessionConfiguration -Name 'KsHelpDesk' -Path .\KsHelpDesk.pssc -Force
Users connect as follows.
Enter-PSSession -ComputerName 'appsrv01' -ConfigurationName 'KsHelpDesk'
# Only permitted commands are available. Restart-Service, too, only for the designated services
There are three key points about JEA.6
- Users do not hold administrator privileges. Execution happens on the virtual account side
- The session is configured as a restricted remote server. The language mode is restricted by default, so arbitrary code cannot be executed
- Transcripts record what was executed. Who did what can be audited
As noted above, the role capability file must sit under a module’s RoleCapabilities folder, and that module must be discoverable from $env:PSModulePath. When a role name specified in RoleDefinitions cannot be resolved, first check with Get-Module -ListAvailable whether the module is visible.6
Deployment assumes that remote execution is configured (see “Getting Started With PowerShell Remoting (WinRM)”).
8. Practical Checklist
| Item | Priority | Status |
|---|---|---|
| Script block logging (4104) enabled | High | Deploy company-wide via GPO18 |
| PowerShell-related log sizes expanded | High | At the defaults they are gone within days |
| Transcription enabled and aggregated to a write-only share | High | Do not keep it locally on the machine1 |
| PowerShell 2.0 engine disabled | High | A bypass route around logging and AMSI3 |
| Antivirus product supports AMSI | High | Enabled by default2 |
| Execution policy not mistaken for a security boundary | Medium | Signing has a different value4 |
| Scope of granted administrator rights reviewed | High | The breadth of privileges determines the blast radius |
| Routine tasks delegated with JEA | Medium | Directly reduces administrator rights6 |
| WDAC/AppLocker considered | Medium | Language mode restriction goes with this5 |
| Logging configuration also deployed to the PowerShell 7 side | Medium | Settings differ between 5.1 and 7 |
9. Summary
- Banning PowerShell has little effect and stops the business. The policy should be “visibility and restriction.”
- The top priority is enabling script block logging (4104). Even obfuscated code is recorded in its expanded form. Do it together with expanding the log size.
- For transcription, the key point is aggregating to a share users cannot delete from.
- Disable the PowerShell 2.0 engine, so as not to leave a route where neither logging nor AMSI applies.
- The execution policy is not a security boundary. Signing has value as tamper detection, but do not make it the core of your defence.
- The breadth of privileges determines the breadth of damage. Delegate “only the operations that are needed” with JEA and you can keep operations running without handing out administrator rights.
Downloading the Sample Code
The code covered in this article is distributed as a package you can run as-is. It includes enabling the logging settings and creating the JEA endpoint.
Download the sample code (zip)
Because the samples in this article depend on Windows and on your tenant, they have not been verified by execution. Syntax parsing and static analysis with PSScriptAnalyzer have been run against every file, but please always confirm the behaviour on your own test machine.
# Syntax parsing + static analysis (runs on non-Windows too)
./Invoke-SampleTests.ps1
Configuration values (paths, server names, tenant IDs, and so on) are examples. Do not run them against production as they stand — adapt them to your own environment.
Related Articles
- PowerShell Execution Policy and Script Signing — A Practical Guide to Graduating From “Papering Over It With Bypass”
- Investigating Event Logs Practically With Get-WinEvent — How Fast You Can Narrow Down Decides How Long the Investigation Takes
- Getting Started With PowerShell Remoting (WinRM) — Managing Multiple Windows Machines at Once
- Handling Credentials Safely in PowerShell — Banishing Plaintext Passwords From Your Scripts
- A Minimum Security Checklist for Windows App Development
- Information Security 10 Major Threats 2026 — How to Read the Ranking, and What SMEs Should Actually Guard Against
Related Consulting Areas
KomuraSoft LLC handles security configuration reviews for Windows operational environments, operational design including privilege delegation (JEA), and consulting on establishing and making use of audit logs.
- Technical Consulting and Design Review
- Bug Investigation and Root-Cause Analysis
- Modification and Maintenance of Existing Windows Software
- Contact
References
-
Microsoft Learn, about_Logging_Windows. On the three kinds of logging (module logging, script block logging, and transcription), enabling them via Group Policy and the registry (under HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell), the fact that script block logging records de-obfuscated code as event ID 4104, that module logging is recorded as event ID 4103, and the OutputDirectory and EnableInvocationHeader settings for transcription. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Antimalware Scan Interface (AMSI). On AMSI being a mechanism by which applications and services pass content to any antimalware product for inspection, and on Windows scripting engines including PowerShell being integrated with it so that even obfuscated scripts can be inspected with their content as of execution time. ↩ ↩2 ↩3
-
Microsoft Learn, Deprecating Windows PowerShell 2.0. On the Windows PowerShell 2.0 engine being deprecated and disabling it being recommended, and on it being provided as a Windows optional feature that can be enabled or disabled. ↩ ↩2 ↩3
-
Microsoft Learn, about_Execution_Policies. On the execution policy not being a security boundary but a safety feature to prevent users from unintentionally running scripts, and on the existence of several ways around it. ↩ ↩2 ↩3
-
Microsoft Learn, about_Language_Modes. On the language elements available in each of FullLanguage, ConstrainedLanguage, RestrictedLanguage, and NoLanguage, on checking the current mode via $ExecutionContext.SessionState.LanguageMode, on PowerShell running in constrained language mode in environments where application control via WDAC or AppLocker is in effect, and on manual language mode configuration not being intended as a security feature. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Just Enough Administration (JEA) Overview. On JEA being a mechanism to delegate only specific administrative tasks without granting administrator privileges, on VisibleCmdlets in a role capability file (.psrc) and restrictions via parameters and ValidateSet, on RestrictedRemoteServer, RunAsVirtualAccount, TranscriptDirectory, and RoleDefinitions in a session configuration file (.pssc), on registration via Register-PSSessionConfiguration, and on the language mode being restricted in a JEA session. For the requirement to place role capability files in a PowerShell module’s RoleCapabilities folder (keeping it discoverable as a module, and creating files with New-PSRoleCapabilityFile), see JEA Role Capabilities. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, JEA Role Capabilities. On the need to define your own functions in FunctionDefinitions and also list their names in VisibleFunctions (“Don’t forget to add the name of your custom functions to the VisibleFunctions field so they can be run by the JEA users.”), on the body of a function (the script block) running in the system default language mode and therefore not being subject to JEA’s restrictions, on needing a fully qualified name (Microsoft.PowerShell.Utility\Select-Object) to use the original implementation of a constrained command that JEA substitutes, on the approach of splitting functions out into a script module and exposing them through VisibleFunctions when there are many, and on the module folder needing a file with the same name as the folder. ↩ ↩2
-
Microsoft Learn, Set-ItemProperty. On -Type being added as a dynamic parameter when using the registry provider, allowing the data type of a registry value (String / ExpandString / Binary / DWord / MultiString / QWord and so on) to be specified. Includes the fact that the value is created if it does not exist. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Automating PC Provisioning With winget + PowerShell — Making the Runbook Executable
How to make new-hire PC setup reproducible. Covers installing applications with winget and export/import, declarative configuration with ...
Investigating Event Logs in Practice with Get-WinEvent — Filtering Speed Decides How Long the Investigation Takes
How to make Windows event log investigation efficient with PowerShell. Covers why filtering with Where-Object is slow, when to use Filter...
Stop Using Write-Host — PowerShell Output Streams and Log Design
How to choose between PowerShell's six output streams, the problems with Write-Host and where it genuinely belongs, why function return v...
Handling Credentials Safely in PowerShell — Banishing Plaintext Passwords from Your Scripts
A practical walkthrough of moving plaintext passwords out of PowerShell scripts and into safe storage: what SecureString really is and wh...
Taking Inventory of a File Server with PowerShell — Capacity Analysis and Access Permission (ACL) Auditing
A practical procedure for taking inventory of a file server with PowerShell. Covers capacity analysis and recording access denials, listi...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Frequently Asked Questions
Common questions about the topic of this article.
- Should we ban PowerShell across the company as a security measure?
- We don't recommend it: it has little real effect and large side effects. PowerShell is the management foundation of Windows itself, and what it actually is is a set of capabilities on top of .NET. Blocking the executable still leaves the same APIs callable by other means, so it is unlikely to be much of an obstacle to an attacker — while it will reliably stop legitimate administration and automation. The realistic policy is not prohibition but visibility and restriction. Record what was executed with script block logging, disable the old engine version, and where necessary narrow what can be run using language modes or JEA.
- If we enable script block logging, won't we be buried in logs?
- The volume does increase, so enable it together with a review of your log size settings. If you leave the maximum size of the Microsoft-Windows-PowerShell/Operational log at its default, it will wrap in a short period and the entries you actually need will be gone when it matters. Operationally, allocate a generous log size and, if necessary, aggregate to a SIEM or via event forwarding. There is also a setting that records more detailed invocation start/stop events, but its output volume is very high, so normally you should enable only the standard script block logging.
- Does setting the execution policy to AllSigned count as a security measure?
- The execution policy is not a security boundary. The official documentation states explicitly that it is a mechanism to stop users from unintentionally running dangerous scripts, not something that stops malicious action — because several ways around it exist. Signing has a separate value of its own, namely being able to verify the integrity of what you distribute, but the core of your defence should be visibility through logs, inspection through AMSI, and restriction of privileges through language modes and JEA.
- Is it fine to set ConstrainedLanguage mode by hand?
- Setting it manually through environment variables and the like is not recommended. It is easy to bypass and does not function as a security feature. Constrained language mode is designed to be used as the result of configuring application control with WDAC (Windows Defender Application Control) or AppLocker, at which point PowerShell switches to it automatically. Narrowing only the language mode without deploying application control does not give you an effective defence.
- I want to let a help desk operator restart one specific service on a server, and nothing else.
- JEA (Just Enough Administration) is the mechanism for exactly that. In a role capability file you define "allow only this value, of this parameter, of this command," and register it as a session configuration. Users can then run only the permitted operations, via a virtual account, without holding administrator privileges themselves. The session is configured as a restricted remote server and by default the language mode is restricted too, so there is no room to execute arbitrary code. What is run can be recorded as a transcript.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links