About the Turkish-I Problem

  • Context

We’ve got some computers located in Turkey. My colleagues started to execute some PowerShell code in the end-user context/session/environment.
They had the following code and noticed that the regular expression on line 5 doesn’t match when the culture is set to tr-TR

(Get-Content -Path $FilePath -ReadCount 1 -Encoding UTF8) | 
ForEach-Object {
 $line = $_
 
 if ($line -match "^([A-Z0-9_]+)=(.*)$") {
  $k = $Matches[1]
  $v = $Matches[2]
   			
  if ($Keys -contains $k) {
   $myData.$k = $v.ToString().Trim()
  } elseif ($WarnIfUnknown) {
   Write-Warning "Unknown ini key '$k' in file '$FilePath'"
  }
 } else {
  Write-Warning "Wrong line '$line' in file '$FilePath'"
 }
}
  • Problem

What happens is actually well documented on this page:

By default, when the regular expression engine performs case-insensitive comparisons, it uses the casing conventions of the current culture to determine equivalent uppercase and lowercase characters.

However, this behavior is undesirable for some types of comparisons, particularly when comparing user input to the names of system resources, such as passwords, files, or URLs. The following example illustrates such as scenario. The code is intended to block access to any resource whose URL is prefaced with FILE://. The regular expression attempts a case-insensitive match with the string by using the regular expression $FILE://. However, when the current system culture is tr-TR (Turkish-Turkey), “I” is not the uppercase equivalent of “i”. As a result, the call to the Regex.IsMatch method returns false, and access to the file is allowed.

Source: https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options

Here’s the same example in PowerShell to showcase what happens:

NB: It uses a function named Using-Culture that you can find on this page.

  • Solution

The solution is provided on the same page that documents the above problem:

Instead of using the case-insensitive comparisons of the current culture, you can specify the RegexOptions.CultureInvariant option to ignore cultural differences in language and to use the conventions of the invariant culture.

Using-Culture -Culture 'tr-TR' -ScriptBlock {
 [regex]::IsMatch(
  'file',
   'FILE',
   [System.Text.RegularExpressions.RegexOptions]::IgnoreCase+
   [System.Text.RegularExpressions.RegexOptions]::CultureInvariant
 )
}

As you can see, it returns now true instead of false:

How could my colleagues solve their issue?
Well, they should use the RegexOptions.CultureInvariant option to ignore cultural differences when they perform their regular expression match.
They cannot use the -match operator anymore and have it populate the $Matches automatic variable when the input is scalar.
Instead of the -match operator, they should use the .Net [regex] object to be able to specify the RegexOptions.CultureInvariant option:

(Get-Content -Path $FilePath -ReadCount 1 -Encoding UTF8) | 
ForEach-Object {
 $line = $_
 
 if ([regex]::Matches($line,'^([A-Z0-9_]+)=(.*)$',513)) {

  $k,$v = [regex]::Matches($line,'^([A-Z0-9_]+)=(.*)$',513) | 
  Select-Object -ExpandProperty Groups | 
  Select -Last 2 -ExpandProperty Value
   			
  if ($Keys -contains $k) {
   $myData.$k = $v.ToString().Trim()
  } elseif ($WarnIfUnknown) {
   Write-Warning "Unknown ini key '$k' in file '$FilePath'"
  }
 } else {
  Write-Warning "Wrong line '$line' in file '$FilePath'"
 }
}
Advertisements

PowerShell Conference Book #PSConfBook

Have you ever been tasked to remove admin privileges to your users or asked to implement a least privilege approach?

Removing User Admin Rights Mitigates 94% of All Critical Microsoft Vulnerabilities.
I wouldn’t be as assertive as this headline about the percentage. I prefer to say instead that removing admin rights reduces the attack surface and that the likelyhood your computers would be far more resistant when there’s a 0day is much higher.

Removing admin rights is for sure a recommended best practice:

Restrict users’ permissions to install and run software applications, and apply the principle of “least privilege” to all systems and services. Restricting these privileges may prevent malware from running or limit its capability to spread through a network.

source:https://www.us-cert.gov/Ransomware

Prevent
source: The above slide is from Ivanti

Have you ever seen anything else than just recommendations and guidelines about how to implement a least privilege strategy?
Have you been able to locate any detailed starting guide about this topic?

I propose a basic detailed least privilege implementation example in a chapter of the PowerShell Conference Book

Are you looking for more good reasons to buy this book?

I’d like to personally thank Mike F Robbins, Michael T. Lombardi and Jeff Hicks.

Remove a DSC config

  • Context:

I’ve recently setup a Desired State Configuration (DSC) configuration on a computer that had the Hyper-V role installed.
The DSC configuration was supposed to apply once and reboot the computer once done.

  • Problem:

I was using a script resource but I failed to make it bulletproof.
The test part of the script resource always failed and returned false when the Hyper-V role was present.
It created a reboot loop.
I had to find a quick way to stop the DSC config to apply and remove it.

  • Solution:
Stop-DscConfiguration -Force -Verbose
Remove-DscConfigurationDocument -Stage Current,Pending -Force -Verbose

DateTime conversion

Recently a colleague of mine asked me why he could do this and couldn’t get the correct date?

(Get-ADUser $UserName -Properties LastLogon).LastLogon |
Get-Date

Well, some properties are stored as a 64bit integer in Active Directory:

Like many other properties found in Active Directory, these 64bit integers represent the number of 100-nanosecond intervals since January 1, 1601 (UTC)

Then why can’t I pipe a 64bit integer into the Get-Date cmdlet and get the correct date?
I can do the following, I can pipe a string and immediately get the correct date:

'31/12/2018' | Get-date

The Get-Date cmdlet will treat the input as a datetime object.
Both the help of the Get-Date cmdlet and its source code shows it.

Why the datetime object does not convert correctly the Active Directory 64bit integer?
The datetime object has many constructors.

[System.DateTime].GetConstructors().GetParameters() | 
Select -Unique | Select Name,Member

DateTime Constructor (Int64) documented on this page says it uses A date and time expressed in the number of 100-nanosecond intervals that have elapsed since January 1, 0001 at 00:00:00.000 in the Gregorian calendar.

Fortunately, there’s a another method documented that deals with ticks elapsed since 1/1/1601 and not 1/1/0001.
The DateTime.FromFileTime Method (Int64) documented on this page says:

A Windows file time expressed in ticks.
A Windows file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC). Windows uses a file time to record when an application creates, accesses, or writes to a file.

This means that I can get the correct date if I do:

[DateTime]::FromFileTime('131787092608430925')
# or
[DateTime]::FromFileTime(
(Get-ADUser $UserName -Properties LastLogon).LastLogon
)

Ok, there’s still some magic happening behind the scene. How do I know what the PowerShell engine does? When does it use a method or a constructor?
There’s an excellent post about this topic on the PowerShell Team blog:
Understanding PowerShell’s Type Conversion Magic

Trace-Command -Expression {  
 '31/12/2018' | Get-date
} -PSHost -Name TypeConversion

Trace-Command -Expression { 
 [int64]'131787092608430925' | Get-date
} -PSHost -Name TypeConversion

Issue with PowerShell Remote Endpoints after a Windows 10 Upgrade

  • Context:

I’ve tested recently an upgrade to the latest branch 1803 (aka Spring Creators Update / RS4) of a Windows 10 Enterprise x64 that had a custom remote endpoint configuration.

  • Issue:

Everything was still there. It was still registered, its ACL still set… except that it doesn’t work.
If you attempt to create a new PSSession or use Enter-PSSession or Invoke-Command, it failed with the same error message:

New-PSSession : [localhost] Connecting to remote server localhost failed with the following error message : The WS-Management service cannot process the operation. An attempt to create a virtual account failed. Ensure that WinRM service is running as Local System and that it has TCB privilege enabled. For more information, see the about_Remote_Troubleshooting Help topic.

TCB means Trusted Computing Base. The WinRM service seems to have lost its (SeTcbPrivilege) ability to Act as part of the operating system and cannot create the virtual account used by the remote endpoint.

If I check the services on a 1709, I’ve:

If I check the services on a 1709 upgraded to a 1803, I’ve:

  • Solution 1:

Unregister and register

  • Solution 2:

There are two ways to restore the TCB privilege: either using sc.exe or by setting the RequiredPrivileges REG_MULTI_SZ value in the registry.

# using sc.exe to restore privilege
sc.exe privs WinRM SeAssignPrimaryTokenPrivilege/SeAuditPrivilege/SeChangeNotifyPrivilege/SeCreateGlobalPrivilege/SeImpersonatePrivilege/SeTcbPrivilege
# or directly setting the value in the registry
$HT = @{
 Path = 'HKLM:\SYSTEM\CurrentControlSet\Services\WinRM'
 Name = 'RequiredPrivileges'
 Value = @(
  'SeAssignPrimaryTokenPrivilege',
  'SeAuditPrivilege',
  'SeChangeNotifyPrivilege',
  'SeCreateGlobalPrivilege',
  'SeImpersonatePrivilege',
  'SeTcbPrivilege'
 )
}
Set-ItemProperty -Type MultiString @HT -Force

# Restore the local system account
Get-CimInstance -ClassName Win32_Service -Filter "Name='WinRM'"| 
Invoke-CimMethod -MethodName Change -Arguments @{StartName='LocalSystem'}

# Apply changes
Restart-Service -Name WinRM
  • Misc.:

Can you notice any other difference left by the upgrade?

The DisplayName was changed and the ServiceType was changed from 0x10 (it has its own process) to 0x20 (it’s a shared process).
Don’t ask me why the upgrade changed these settings? 🙄 I have no idea.

Bonus: Solution 1 is better than solution 2 because it also fixes the service type.

Detect and correct orphaned ‘adminCount=1’ in Active Directory

I recently needed to detect and correct orphaned ‘adminCount=1’ in Active Directory.
I googled that old problem and found the following code: https://gist.github.com/webash/b34c5a422288827ff4e53318e34c6923

I loved his honesty and the fact that he respected the best practice of adding a note where he has acquired the code.
The help also references a blog post on technet.

I don’t like to reinvent the wheel and I couldn’t resist fixing the code anyway.

Register a remote endpoint with DSC

  • Context:

I’ve been using Desired State Configuration (DSC) recently on a Windows 10 computer to create a custom remote endpoint configuration.

  • Issues:

Using the Register-PSSessionConfiguration with a -Force switch parameter breaks the push.
The error message says:
The WS-Management service cannot process the operation. The operation is being attempted on a client session that is unusable. This may be related to a recent restart of the WS-Management service. Please create a new client session and retry the operation if re-executing the operation does not have undesired behavior.
+ CategoryInfo : InvalidOperation: (root/Microsoft/…gurationManager:String) [], CimException
+ FullyQualifiedErrorId : HRESULT 0x803381fa
+ PSComputerName : localhost

If you specify a -Verbose switch parameter when you push the configuration with Start-DscConfiguration cmdlet, the verbose stream of the Register-PSSessionConfiguration cmdlet is still streamed although it’s explicitly turned off.

  • Solution:

The solution consisted in:

  1. turning off the verbose stream from the Register-PSSessionConfiguration cmdlet by enclosing this cmdlet in scriptblock executed by the Start-Job cmdlet.
  2. removing the -Force switch parameter that restarts and breaks the push
  3. using a -NoRestartService switch parameter with the Register-PSSessionConfiguration cmdlet