dsccommunity / ActiveDirectoryDsc

This module contains DSC resources for deployment and configuration of Active Directory Domain Services.
MIT License
336 stars 140 forks source link

ADSRootKey: Fails with 'String was not recognized as a valid DateTime' exception #702

Closed Borgquite closed 7 months ago

Borgquite commented 7 months ago

Problem description

Trying to use KDSRootKey to validate an existing KDSRootKey on a server with the English (United Kingdom) locale set.

Receiving this error:

Verbose logs

VERBOSE: [REDACTED]: LCM:  [ Start  Resource ]  [[ADKDSKey]ADKDSKey::[DomainController]REDACTED]                                                                                                                  VERBOSE: [REDACTED]: LCM:  [ Start  Test     ]  [[ADKDSKey]ADKDSKey::[DomainController]REDACTED]                                                                                                                  VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Retrieving KDS Root Key with effective date of '<redacted>'. (KDSK0001)                        VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Perform operation 'Enumerate CimInstances' with following parameters, ''namespaceName' = root\cimv2,'className'  = Win32_OperatingSystem'.                                                                                                                                                                                       VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Operation 'Enumerate CimInstances' complete.                                                                     VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Checking if the user 'NT AUTHORITY\SYSTEM' has valid Domain Admin permissions. (KDSK0019)                        VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Checking if the node 'REDACTED' is a Domain Controller. The node has a product type of '2'. If the product type   is 2, then it is a domain controller. (KDSK0020)                                                                                                                                                                VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Found KDS Root Key with the effective date of '<redacted>'. (KDSK0010)                         WARNING: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Found more than one KDS Root Keys. This shouldn't be an issue, but having only one key per domain is             recommended. (KDSK0009)                                                                                                                                                                                         
Exception calling "Parse" with "1" argument(s): "String was not recognized as a valid DateTime."
    + CategoryInfo          : NotSpecified: (:) [], CimException
    + FullyQualifiedErrorId : FormatException
    + PSComputerName        : REDACTED

VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] Retrieved the root domain distinguished name of '<redacted>'. (KDSK0021)
VERBOSE: [REDACTED]:                            [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] KDS Root Key with the effective date of '2024-01-25T10:59:31.0141932Z' is in the desired state. (KDSK0015)
VERBOSE: [REDACTED]: LCM:  [ End    Test     ]  [[ADKDSKey]ADKDSKey::[DomainController]REDACTED] False in 0.3600 seconds.
VERBOSE: [REDACTED]: LCM:  [ *FAILED*Compare  ]     Completed processing compare operation. The operation returned False.
The PowerShell DSC resource '[ADKDSKey]ADKDSKey::[DomainController]REDACTED' with SourceInfo '' threw one or more non-terminating errors while running the Test-TargetResource functionality. These errors are 
logged to the ETW channel called Microsoft-Windows-DSC/Operational. Refer to this channel for more details.
    + CategoryInfo          : InvalidOperation: (root/Microsoft/...gurationManager:String) [], CimException
    + FullyQualifiedErrorId : NonTerminatingErrorFromProvider
    + PSComputerName        : REDACTED

DSC configuration

ADKDSKey "ADKDSKey"
{
     EffectiveTime = "<redacted>"
     AllowUnsafeEffectiveTime = $true
}

Suggested solution

Having stepped through the code in MSFT_ADKDSKey.psm1 it looks like this is failing in Get-TargetResource, line 110. If I run these commands on my system:

$effectiveTimeObject = [DateTime]::Parse("01 February 2024 12:21:51")
$kdsRootKeys = Get-KdsRootKey
    $kdsRootKey = $null
    if ($kdsRootKeys)
    {
        $kdsRootKey = $kdsRootKeys.GetEnumerator() |
            Where-Object -FilterScript {
                $_.EffectiveTime -eq $effectiveTimeObject
            }
    }
$targetResource['EffectiveTime'] = ([DateTime]::Parse($kdsRootKey.EffectiveTime)).ToString()

I get:

Exception calling "Parse" with "1" argument(s): "String was not recognized as a valid DateTime."
At line:1 char:1
+ $targetResource['EffectiveTime'] = ([DateTime]::Parse($kdsRootKey.Eff ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

Running '$kdsRootKey.EffectiveTime' returns the value '01 February 2024 12:21:51', but this is not accepted by [DateTime]::Parse() - possibly down to system locale (which is en-GB)? To be honest I'm not sure why we are running [DateTime]::Parse() on $kdsRootKey.EffectiveTime in line 110 or line 423 anyway, as it's already a [DateTime] object, so this could be a fix?

Operating system the target node is running

OsName               : Microsoft Windows Server 2022 Standard
OsOperatingSystemSKU : StandardServerEdition
OsArchitecture       : 64-bit
WindowsVersion       : 2009
WindowsBuildLabEx    : 20348.1.amd64fre.fe_release.210507-1500
OsLanguage           : en-US
OsMuiLanguages       : {en-US}

PowerShell version and build the target node is running

Name                           Value
----                           -----
PSVersion                      5.1.20348.2227
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.20348.2227
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

ActiveDirectoryDsc version

Name               Version Path
----               ------- ----
ActiveDirectoryDsc 6.3.0   C:\Program Files\WindowsPowerShell\Modules\ActiveDirectoryDsc\6.3.0\ActiveDirectoryDsc....
johlju commented 7 months ago

This is most likely trying to return the value from DateTime to a string. Since the parameter EffectiveTime is a string.

https://github.com/dsccommunity/ActiveDirectoryDsc/blob/b89e4f0f8277b168171a3fe802f8473a9288f057/source/DSCResources/MSFT_ADKDSKey/MSFT_ADKDSKey.psm1#L110

Because Get-TargetResource returns EffectiveTime as string (need by the schema, and done by line 110 above) the line at 423 seems to try to convert the value back to DateTime. Line 422 and 423 prepares the values as DateTime so the comparison compares two DateTime values (instead of comparing strings).

https://github.com/dsccommunity/ActiveDirectoryDsc/blob/b89e4f0f8277b168171a3fe802f8473a9288f057/source/DSCResources/MSFT_ADKDSKey/MSFT_ADKDSKey.psm1#L422-L423

The problem seems to be that the parsing should always be done through a known format, that works regardless of locale. 🤔

johlju commented 7 months ago

Maybe line 110 should be changed to something like this, assuming the property EffectiveTime is a DateTime in all supported OS:

$targetResource['EffectiveTime'] = $kdsRootKey.EffectiveTime.ToString('yyyy-MM-dd HH:mm:ss.fff')

or

$targetResource['EffectiveTime'] = $kdsRootKey.EffectiveTime.GetDateTimeFormats('o')
Borgquite commented 7 months ago

On your last point, the property EffectiveTime appears to be a DateTime on Windows Server 2012 R2 and Windows Server 2022 - doubt it's changed between?

OsName               : Microsoft Windows Server 2012 R2 Standard
OsOperatingSystemSKU : StandardServerEdition
OsArchitecture       : 64-bit
WindowsBuildLabEx    : 9600.21620.amd64fre.winblue_ltsb_escrow.230929-1158
OsLanguage           : en-US
OsMuiLanguages       : {en-US}

PS C:\Windows\system32> (Get-KdsRootKey)[1].EffectiveTime.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     DateTime                                 System.ValueType
OsName               : Microsoft Windows Server 2022 Standard
OsOperatingSystemSKU : StandardServerEdition
OsArchitecture       : 64-bit
WindowsVersion       : 2009
WindowsBuildLabEx    : 20348.1.amd64fre.fe_release.210507-1500
OsLanguage           : en-US
OsMuiLanguages       : {en-US}

PS C:\Windows\system32> (Get-KdsRootKey)[1].EffectiveTime.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     DateTime                                 System.ValueType
Borgquite commented 7 months ago

OK so there's a load of weirdness going on when DateTime attempts to process dates in non-US cultures, in what I'm pretty sure is buggy behaviour (see my report at https://stackoverflow.com/questions/77968235/how-is-powershell-datetimeparse-processing-broken-in-certain-locales)

But it looks like all that's needed to fix it here is to bypass the unnecessary Parse() call - have tested the attached PR on my system & seems to work perfecly now.