Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple resources failing to return correct result on test #500

Open
modfh98 opened this issue Dec 1, 2023 · 26 comments
Open

Multiple resources failing to return correct result on test #500

modfh98 opened this issue Dec 1, 2023 · 26 comments
Labels
bug The issue is a bug. help wanted The issue is up for grabs for anyone in the community.

Comments

@modfh98
Copy link

modfh98 commented Dec 1, 2023

Hi,

Is anyone aware of any recent changes (possibly November updates) that have started causing issues with this module? We've been using it for some time and I've recently noticed there are multiple resources where the test-targetresource function is continually returning as false. I've started looking further into the ExchExchangeCertificate resource which is one of them but the others listed below are also failing to test correctly on each run
My initial thoughts are that it might be something to do with how it's comparing arrays, but I haven't been able to determine anything conclusive yet. I can start digging further if needs be but I'm wondering if this is a known problem before I do so?

ExchReceiveConnector
ExchOutlookanywhere
ExchDatabaseAvailabilityGroupMember
ExchAutoMountPoint
ExchMailboxDatabase
ExchMailboxDatabaseCopy

@mhincapie
Copy link
Contributor

mhincapie commented Dec 1, 2023

@modfh98 , I think this relates to the latest Security Updates (SU) changes. In the last one posted here:

Microsoft has enabled Powershell Serialization by default. These have brought some known issues that are listed here:

We utilize this Powershell module quite often. Do you also? We could try to see if we can modify the code so it's compatible with Powershell serialization. What do you think? also, do you have any suggestions @johlju ?

@modfh98
Copy link
Author

modfh98 commented Dec 1, 2023

@mhincapie
The article suggests that the problem is mainly affecting piped cmdlets on management tools boxes, but I think you're right and this has had some other effects. It was also my suspicion. They have mentioned that MS is working on a fix.. so maybe this will be sorted next round of updates?
🤞

@modfh98 modfh98 closed this as completed Dec 1, 2023
@modfh98 modfh98 reopened this Dec 1, 2023
@modfh98 modfh98 changed the title Multiple ressources failing to return correct result on test Multiple resources failing to return correct result on test Dec 1, 2023
@johlju
Copy link
Member

johlju commented Dec 1, 2023

Awesome that you help each other finding the cause, I wouldn't have know about this, thanks @mhincapie. If you see the fix from MS i dragging out I'm happy to review a PR that uses the suggested workaround.

@modfh98 maybe you can change the code, according to the suggested workaround, locally for one of the resources (suggest take the simplest one) on one of your nodes just to verify that it actually quick-fixes the problem?

@DelectableMechanisations
Copy link
Contributor

Did anyone have any success implementing a workaround for this?

I spent several hours troubleshooting the ExchExchangeCertificate resource with DSC Debug enabled but wasn't able to make any headway.
When I stepped in via the debugger, I got through to the point where the Get-ExchangeCertificate command is called and found it was returning an object of type:
Deserialized.Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificate

I could successfully convert this object into the standard certificate type:
System.Security.Cryptography.X509Certificates.X509Certificate2
And this allowed me to read the standard properties (Thumbprint, NotAfter, Subject etc).

But I wasn't able to convert it back into its correct type:
Microsoft.Exchange.Management.SystemConfigurationTasks.ExchangeCertificate

Thus, the object was missing the "Services" property was what was causing the Test-TargetResource to fail.
Without the "Services" property, you can't use this resource to bind a certificate to any services (IIS, IMAP etc) so I've had to comment out all sections of my configuration document that relate to ExchExchangeCertificate.

Like @modfh98, I've had issues with some of the resources they referenced including:

  • ExchReceiveConnector - Cannot process property ExtendedRightAllowEntries
  • ExchOutlookAnywhere - Cannot process properties InternalHostname and ExternalHostname

I haven't done any significant troubleshooting for these so it's possible there's a simpler workaround for these.

When I disabled certificate signing of PowerShell serialization payloads using the method below, this problem disappeared.
https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019#disable-certificate-signing-of-powershell-serialization-payloads

We enabled certificate signing of PowerShell serialization payloads back in February 2023 and it's been a problem ever since.
I am seriously considering abandoning using DSC for Exchange as my configuration document is going to be full of commented out sections of code for resources that no longer work.

I would have thought Microsoft would have invested some more time in maintaining this resource.
What does Microsoft use to configure its fleet of Exchange Online servers? Surely DSC is the most efficient way to keep the configuration aligned across their Exchange Online fleet.
Or do they have another tool for that?

@gborus
Copy link
Contributor

gborus commented Mar 18, 2024

I have the same concern, was expecting MSFT fix the serialization issue by now.
Many folks using DSC for building and maintaining Xch servers, which is not possible using the module anymore.

Is there any timeline for the SU issue fix, does anybody know?

thanks,

@gborus
Copy link
Contributor

gborus commented Apr 8, 2024

Do you guys know if there is any MSFT internal discussion how the DSC issue caused by ps payload siging will be solved or will be solved at all?
Many thanks,

@raandree
Copy link
Contributor

I am trying to get into this a bit more and need a very simple configuration to start with. So far I cannot reproduce the issue. At the very top it is mentioned that the issue affects the resource ExchReceiveConnector. This configuration is running fine on an Exchange Server 2019 CU14 with EnableSigningVerification.

configuration c1 {

    Import-DscResource -ModuleName ExchangeDsc

    $cred = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))

    node localhost {
        ExchReceiveConnector rc1 {
            Identity = 'MSEEx1\rc1'
            Credential = $cred
            Ensure = 'Present'
            Bindings = '0.0.0.0:2525'
            RemoteIPRanges = '192.168.0.1-192.168.0.24'
            Usage = 'Custom'
        }
    }
}

$cd = @{
    AllNodes = @(
        @{
            NodeName = 'localhost'
            PSDscAllowPlainTextPassword = $true
        }
    )
}

c1 -OutputPath c:\dsc -ConfigurationData $cd

Start-DscConfiguration -Path C:\dsc -Wait -Verbose -Force
Get-ExchangeDiagnosticInfo -Process Microsoft.Exchange.Directory.TopologyService -Component VariantConfiguration -Argument Refresh
RunspaceId  : 125eeb6d-1eb2-4362-9bd5-854356340c77
Result      : <Diagnostics>
                <ProcessInfo>
                  <id>6052</id>
                  <serverName>MSEEx1</serverName>
                  <startTime>2024-05-25T07:55:31.7262229Z</startTime>
                  <currentTime>2024-05-25T08:09:32.9996174Z</currentTime>
                  <lifetime>00:14:01.2733945</lifetime>
                  <threadCount>16</threadCount>
                  <handleCount>1317</handleCount>
                  <workingSet>132.9 MB (139,321,344 bytes)</workingSet>
                  <fastTrainExchangeVersion>15.2.1544.4</fastTrainExchangeVersion>
                </ProcessInfo>
                <Components>
                  <VariantConfiguration>
                    <Overrides Updated="2024-05-25T08:09:33.0056164Z">
                      <SettingOverride>
                        <Name>EnableSigningVerification</Name>
                        <Reason>Enabling Signing Verification</Reason>
                        <ModifiedBy>contoso.com/Users/Install</ModifiedBy>
                        <ComponentName>Data</ComponentName>
                        <SectionName>EnableSerializationDataSigning</SectionName>
                        <Status>Accepted</Status>
                        <Message>This override synced to the server but whether it applies to the services running on
              this server depends on the override parameters, current configuration and the context.</Message>
                        <Parameters>
                          <Parameter>Enabled=true</Parameter>
                        </Parameters>
                      </SettingOverride>
                    </Overrides>
                  </VariantConfiguration>
                </Components>
              </Diagnostics>
Identity    :
IsValid     : True
ObjectState : New

What do I need to do to run into the issue?

@raandree
Copy link
Contributor

@modfh98, is this issue also happening on Exchange 2019 CU14 or only with Exchange 2016? On Exchange 2019 CU14 everything works just fine for me.

@raandree
Copy link
Contributor

@johlju, can you flag this issue as a bug, please?

@raandree
Copy link
Contributor

raandree commented Aug 29, 2024

I am not an expert in Exchange, so I cannot comment on the cause. This is why for example the ExchMailboxDatabase resource doesn't work anymore:

The object type of some values has been changed, probably because of what @mhincapie explained above. In

if (!(Test-ExchangeSetting -Name 'EdbFilePath' -Type 'String' -ExpectedValue $EdbFilePath -ActualValue $db.EdbFilePath.PathName -PSBoundParametersIn $PSBoundParameters -Verbose:$VerbosePreference))
tries to access the PathName property which does not exist anymore as $db.LogFolderPath is a string.

The same happens here:

The solution would be to change the code so that is uses the value stored in the property PathName if that property exist, otherwise it uses the value of $db.LogFolderPath.

@gborus, can you give it a try and change these 3 lines, please?

Given the fact that there has been no feedback so far, is ExchangeDsc still being used at all?

@johlju johlju added bug The issue is a bug. help wanted The issue is up for grabs for anyone in the community. labels Aug 29, 2024
@gborus
Copy link
Contributor

gborus commented Aug 30, 2024

Hi @raandree , the suggested solution would solve this particular problem, although this would mean all resources' script would need to be review line by line to spot similar problems, not mentioning cases like the DB copies' activation preference query which data is available at the 3rd level, like database.databasecopies.activation preference, which data gets lost when database.databasecopies gets converted to string. And the trick with this value, activation preference, that it is really hard to collect it from elsewhere other than the get-mailboxdatabase cmdlet.

thanks,

@raandree
Copy link
Contributor

I have isolated the code that creates the connection to Exchange in the module ExchangeDsc. We believed that the issue could be related to the fact that the DSC LCM runs in the computer’s user context. However, the code works in the user context as well as in the computer context. Works here means that the objects are successfully deserialized. The type of a mailbox database is Microsoft.Exchange.Data.Directory.SystemConfiguration.MailboxDatabase. In the non-working scenario it is just a PSObject and

The script to reproduce the issue is this the following. The code is taken from the ExchangeDsc module.

function Invoke-DotSourcedScript {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ScriptPath,

        [Parameter()]
        [System.Collections.Hashtable]
        $ScriptParams = @{ },

        [Parameter()]
        [System.String[]]
        $SnapinsToRemove,

        [Parameter()]
        [System.String[]]
        $CommandsToExecuteInScope,

        [Parameter()]
        [System.Collections.Hashtable]
        $ParamsForCommandsToExecuteInScope
    )

    [System.Collections.Hashtable] $returnValues = @{ }

    . $ScriptPath @ScriptParams

    for ($i = 0; $i -lt $CommandsToExecuteInScope.Count; $i++) {
        [System.String] $commandToExecute = $CommandsToExecuteInScope[$i]

        [System.Collections.Hashtable] $commandParams = @{ }

        if ($ParamsForCommandsToExecuteInScope.ContainsKey($commandToExecute)) {
            [System.Collections.Hashtable] $commandParams = $ParamsForCommandsToExecuteInScope[$commandToExecute]
        }

        $returnValue = . $commandToExecute @commandParams

        if (!$returnValues.ContainsKey($commandToExecute)) {
            $returnValues.Add($commandToExecute, $null)
        }

        $returnValues[$commandToExecute] = $returnValue
    }

    if ($SnapinsToRemove.Count -gt 0) {
        Remove-HelperSnapin -SnapinsToRemove $SnapinsToRemove -Verbose:$VerbosePreference
    }

    return $returnValues
}

$DSCExchangeModulePath = 'C:\Temp'

$Credential = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
$serverFQDN = 'MSEEx1.contoso.com'

$exbin = Join-Path -Path ((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup).MsiInstallPath) -ChildPath 'bin'
$remoteExchange = Join-Path -Path "$exbin" -ChildPath 'RemoteExchange.ps1'

$commandToExecuteAfterDotSourcing = @('_NewExchangeRunspace')
$commandParamsToExecuteAfterDotSourcing = @{
    '_NewExchangeRunspace' = @{
        fqdn             = $serverFQDN
        credential       = $Credential
        UseWIA           = $false
        AllowRedirection = $false
    }
}

$returnValues = Invoke-DotSourcedScript `
    -ScriptPath $remoteExchange `
    -CommandsToExecuteInScope $commandToExecuteAfterDotSourcing `
    -ParamsForCommandsToExecuteInScope $commandParamsToExecuteAfterDotSourcing `
    -Verbose:$VerbosePreference

if ($null -ne $returnValues -and $returnValues.ContainsKey('_NewExchangeRunspace')) {
    $session = $returnValues['_NewExchangeRunspace']
}

if ($null -ne $session) {
    $session.Name = 'DSCExchangeSession'
}

New-Item -Path $DSCExchangeModulePath -Type Directory -Force | Out-Null
Export-PSSession -Session $Session -OutputModule $DSCExchangeModulePath -Force -AllowClobber | Out-Null
Import-Module -Name $DSCExchangeModulePath -Global -DisableNameChecking -Function *

$dbs = Get-MailboxDatabase
$dbs[0].LogFolderPath

The serialized data is in $dbs[0].psextended.SerializationData but standard .net deserialization methods don’t work. Why is serialization and deserialization not working only when the script is invoked by DSC? How can we get more information on why the deserialization isn’t working?

@johlju
Copy link
Member

johlju commented Oct 25, 2024

Just adding some thoughts, I don't have a clue what could be the difference between running it locally and in DSC.

If it is related to certificate signing of PowerShell serialization payloads as mentioned in an above comment could it be that the user the resource runs at does not have the necessary user rights or permission? Or could it be that the session that is exported is using a different account so the signing is invalid for some reason. 🤔

@raandree
Copy link
Contributor

Thank, @johlju. I am not sure which certificate is used. There are no certificates in the user's scope but some in the computer's one. The output of the certificates is the same in a working and non-working process:

[DBG]: [Process:16540]: [Runspace1]: PS C:\Windows\system32>> dir Cert:\LocalMachine\My | ft Thumbprint, FriendlyName, HasPrivateKey, @{ Label = 'Size'; Expression = { $_.RawData.Count } }

Thumbprint                               FriendlyName                               HasPrivateKey Size
----------                               ------------                               ------------- ----
F129EBDA0E369B30AC40E97822E9DFAD9FC871C5 WMSVC-SHA2                                          True  749
891BC6682439148F819CE8767D5B6C56DC8D8468 TenantEncryptionCert                                True  766
5FB95F81A82D45DE6FE03D8481D7BAE2C85AF1D5 Microsoft Exchange                                  True  780
0612D17CA477F2FC4351F598509A47D87A181418 Microsoft Exchange Server Auth Certificate          True  813
02718F2759C00662CA5D8E07ACED55CF4730F857 TenantEncryptionCert                                True  766

@johlju
Copy link
Member

johlju commented Oct 28, 2024

It says in the article https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019 that the certificate is "Exchange Server Auth Certificate"

"You can use the MonitorExchangeAuthCertificate script to check if the Exchange Server Auth Certificate is valid."

Is it possible to disable signing just to confirm that it is actually signing that is the issue: https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019#disable-certificate-signing-of-powershell-serialization-payloads

@johlju
Copy link
Member

johlju commented Oct 28, 2024

Also, have you tried the code you tested locally above in a DSC resource too so that there aren't anything in the actual DSC resource that fails. In the article above it states that piping to commands could fail too. So maybe try to minimize the code so few lines as possible in a DSC resource to see if it starts to work? 🤔

@raandree
Copy link
Contributor

It says in the article https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019 that the certificate is "Exchange Server Auth Certificate"

"You can use the MonitorExchangeAuthCertificate script to check if the Exchange Server Auth Certificate is valid."

Is it possible to disable signing just to confirm that it is actually signing that is the issue: https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019#disable-certificate-signing-of-powershell-serialization-payloads

Thanks. The DSC LCM process can access the Exchange Server Auth Certificate certificate.

And yes, the customer and we made sure that the issue only exists when signing is enabled.

@raandree
Copy link
Contributor

Also, have you tried the code you tested locally above in a DSC resource too so that there aren't anything in the actual DSC resource that fails. In the article above it states that piping to commands could fail too. So maybe try to minimize the code so few lines as possible in a DSC resource to see if it starts to work? 🤔

You mean something like this? The working code I have shared above fails when it runs in the LCM context.

Configuration MyDscConfiguration {
   param
   (
       [PSCredential]$Creds
   )
   Import-DscResource -Module ExchangeDsc
   Node MSEEx1 {

       Script SetupExchangeConnect
       {
            GetScript = {
                return @{ Result = '' }
            }

            TestScript = {
                return $false
            }

            SetScript = {
                function Invoke-DotSourcedScript {
                    [CmdletBinding()]
                    [OutputType([System.Collections.Hashtable])]
                    param
                    (
                        [Parameter(Mandatory = $true)]
                        [System.String]
                        $ScriptPath,

                        [Parameter()]
                        [System.Collections.Hashtable]
                        $ScriptParams = @{ },

                        [Parameter()]
                        [System.String[]]
                        $SnapinsToRemove,

                        [Parameter()]
                        [System.String[]]
                        $CommandsToExecuteInScope,

                        [Parameter()]
                        [System.Collections.Hashtable]
                        $ParamsForCommandsToExecuteInScope
                    )

                    [System.Collections.Hashtable] $returnValues = @{ }

                    . $ScriptPath @ScriptParams

                    for ($i = 0; $i -lt $CommandsToExecuteInScope.Count; $i++) {
                        [System.String] $commandToExecute = $CommandsToExecuteInScope[$i]

                        [System.Collections.Hashtable] $commandParams = @{ }

                        if ($ParamsForCommandsToExecuteInScope.ContainsKey($commandToExecute)) {
                            [System.Collections.Hashtable] $commandParams = $ParamsForCommandsToExecuteInScope[$commandToExecute]
                        }

                        $returnValue = . $commandToExecute @commandParams

                        if (!$returnValues.ContainsKey($commandToExecute)) {
                            $returnValues.Add($commandToExecute, $null)
                        }

                        $returnValues[$commandToExecute] = $returnValue
                    }

                    if ($SnapinsToRemove.Count -gt 0) {
                        Remove-HelperSnapin -SnapinsToRemove $SnapinsToRemove -Verbose:$VerbosePreference
                    }

                    return $returnValues
                }

                $DSCExchangeModulePath = 'C:\Temp'

                $Credential = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
                $serverFQDN = 'MSEEx1.contoso.com'

                $exbin = Join-Path -Path ((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup).MsiInstallPath) -ChildPath 'bin'
                $remoteExchange = Join-Path -Path "$exbin" -ChildPath 'RemoteExchange.ps1'

                $commandToExecuteAfterDotSourcing = @('_NewExchangeRunspace')
                $commandParamsToExecuteAfterDotSourcing = @{
                    '_NewExchangeRunspace' = @{
                        fqdn             = $serverFQDN
                        credential       = $Credential
                        UseWIA           = $false
                        AllowRedirection = $false
                    }
                }

                $returnValues = Invoke-DotSourcedScript `
                    -ScriptPath $remoteExchange `
                    -CommandsToExecuteInScope $commandToExecuteAfterDotSourcing `
                    -ParamsForCommandsToExecuteInScope $commandParamsToExecuteAfterDotSourcing `
                    -Verbose:$VerbosePreference

                if ($null -ne $returnValues -and $returnValues.ContainsKey('_NewExchangeRunspace')) {
                    $session = $returnValues['_NewExchangeRunspace']
                }

                if ($null -ne $session) {
                    $session.Name = 'DSCExchangeSession'
                }

                New-Item -Path $DSCExchangeModulePath -Type Directory -Force | Out-Null
                Export-PSSession -Session $Session -OutputModule $DSCExchangeModulePath -Force -AllowClobber | Out-Null
                Import-Module -Name $DSCExchangeModulePath -Global -DisableNameChecking -Function *

                Wait-Debugger
                $dbs = Get-MailboxDatabase
                $dbs[0].LogFolderPath
            }
       }
   }
}
$HostHash = @{
    AllNodes = @(
           @{
               NodeName = "$env:COMPUTERNAME"
               PSDscAllowPlainTextPassword = $true
               PSDscAllowDomainUser = $true
           };
       );
}
mkdir "C:\ExConfig" -ErrorAction SilentlyContinue
$myCred = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
MyDscConfiguration -ConfigurationData $HostHash -Creds $mycred -OutputPath C:\ExConfig
Start-DscConfiguration C:\ExConfig -Force -Wait -Verbose

@gborus
Copy link
Contributor

gborus commented Oct 29, 2024

Is it possible to disable signing just to confirm that it is actually signing that is the issue: https://learn.microsoft.com/en-us/exchange/plan-and-deploy/post-installation-tasks/security-best-practices/exchange-serialization-payload-sign?view=exchserver-2019#disable-certificate-signing-of-powershell-serialization-payloads

we disabled payload signing and that solved the problem, otherwise we couldn't get DSC working...

@johlju
Copy link
Member

johlju commented Oct 29, 2024

@raandree What happens if you run the resource as CONTOSO\install as well using property PsDscRunAsCredential? Then the resource runs as the same user as used inside the resource 🤔

@raandree
Copy link
Contributor

Thanks for the hint, @johlju. That did the trick and things are working again. However, I have no idea why it is necessary to use alternative credentials as the LCM should have access to all relevant resources including the certificates and their private keys.

If anyone can explain why using PsDscRunAsCredential solves the problem, please enlighten us.

@gborus, can you please test this in your environment as well? This configuration works for me:

configuration ExchangeMailboxConfig {
    param
    (
        [PSCredential]$Creds
    )

    Import-DscResource -Module ExchangeDsc

    Node MSEEx1 {

        LocalConfigurationManager
        {
            ConfigurationMode              = 'ApplyAndMonitor'
            RebootNodeIfNeeded             = $true
            ActionAfterReboot              = 'ContinueConfiguration'
            ConfigurationModeFrequencyMins = 1200
            RefreshFrequencyMins           = 1200
        }

        ExchMailboxDatabase DBSetup
        {
            Name                   = 'DB1'
            Credential             = $Creds
            EdbFilePath            = 'C:\Program Files\Microsoft\Exchange Server\V15\Mailbox\DB1\DB1.edb'
            LogFolderPath          = 'C:\Program Files\Microsoft\Exchange Server\V15\Mailbox\DB1'
            Server                 = 'MSEEx1'
            CircularLoggingEnabled = $true
            DatabaseCopyCount      = 2
            PsDscRunAsCredential   = $Creds
        }

        ExchMailboxDatabaseCopy DBCopySetup
        {
            Identity             = 'DB1'
            Credential           = $Creds
            MailboxServer        = 'MSEDev'
            ActivationPreference = 1
            PsDscRunAsCredential = $Creds
        }
    }
}
$configData = @{
    AllNodes = @(
        @{
            NodeName                    = $env:COMPUTERNAME
            PSDscAllowPlainTextPassword = $true
            PSDscAllowDomainUser        = $true
        }
    )
}

Remove-Item -Path C:\DSC\* -ErrorAction SilentlyContinue
mkdir -Path C:\DSC -ErrorAction SilentlyContinue
mkdir -Path C:\ExchangeDatabases -ErrorAction SilentlyContinue

$cred = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
ExchangeMailboxConfig -ConfigurationData $configData -Creds $cred -OutputPath C:\DSC

Set-DscLocalConfigurationManager C:\DSC -Verbose

Start-DscConfiguration C:\DSC -Force -Wait -Verbose

Also the script to only test the connection and return a path object works as expected again:

configuration ExchangeMailboxConfig {
    param
    (
        [PSCredential]$Creds
    )

    Import-DscResource -Module ExchangeDsc

    Node MSEEx1 {

        LocalConfigurationManager
        {
            ConfigurationMode              = 'ApplyAndMonitor'
            RebootNodeIfNeeded             = $true
            ActionAfterReboot              = 'ContinueConfiguration'
            ConfigurationModeFrequencyMins = 1200
            RefreshFrequencyMins           = 1200
        }

        Script SetupExchangeConnect
        {
            GetScript            = {
                return @{ Result = '' }
            }

            TestScript           = {
                return $false
            }

            SetScript            = {
                function Invoke-DotSourcedScript
                {
                    [CmdletBinding()]
                    [OutputType([System.Collections.Hashtable])]
                    param
                    (
                        [Parameter(Mandatory = $true)]
                        [System.String]
                        $ScriptPath,

                        [Parameter()]
                        [System.Collections.Hashtable]
                        $ScriptParams = @{ },

                        [Parameter()]
                        [System.String[]]
                        $SnapinsToRemove,

                        [Parameter()]
                        [System.String[]]
                        $CommandsToExecuteInScope,

                        [Parameter()]
                        [System.Collections.Hashtable]
                        $ParamsForCommandsToExecuteInScope
                    )

                    [System.Collections.Hashtable] $returnValues = @{ }

                    . $ScriptPath @ScriptParams

                    for ($i = 0; $i -lt $CommandsToExecuteInScope.Count; $i++)
                    {
                        [System.String] $commandToExecute = $CommandsToExecuteInScope[$i]

                        [System.Collections.Hashtable] $commandParams = @{ }

                        if ($ParamsForCommandsToExecuteInScope.ContainsKey($commandToExecute))
                        {
                            [System.Collections.Hashtable] $commandParams = $ParamsForCommandsToExecuteInScope[$commandToExecute]
                        }

                        $returnValue = . $commandToExecute @commandParams

                        if (!$returnValues.ContainsKey($commandToExecute))
                        {
                            $returnValues.Add($commandToExecute, $null)
                        }

                        $returnValues[$commandToExecute] = $returnValue
                    }

                    if ($SnapinsToRemove.Count -gt 0)
                    {
                        Remove-HelperSnapin -SnapinsToRemove $SnapinsToRemove -Verbose:$VerbosePreference
                    }

                    return $returnValues
                }

                $DSCExchangeModulePath = 'C:\Temp'

                $Credential = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
                $serverFQDN = 'MSEEx1.contoso.com'

                $exbin = Join-Path -Path ((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup).MsiInstallPath) -ChildPath 'bin'
                $remoteExchange = Join-Path -Path "$exbin" -ChildPath 'RemoteExchange.ps1'

                $commandToExecuteAfterDotSourcing = @('_NewExchangeRunspace')
                $commandParamsToExecuteAfterDotSourcing = @{
                    '_NewExchangeRunspace' = @{
                        fqdn             = $serverFQDN
                        credential       = $Credential
                        UseWIA           = $false
                        AllowRedirection = $false
                    }
                }

                $returnValues = Invoke-DotSourcedScript `
                    -ScriptPath $remoteExchange `
                    -CommandsToExecuteInScope $commandToExecuteAfterDotSourcing `
                    -ParamsForCommandsToExecuteInScope $commandParamsToExecuteAfterDotSourcing `
                    -Verbose:$VerbosePreference

                if ($null -ne $returnValues -and $returnValues.ContainsKey('_NewExchangeRunspace'))
                {
                    $session = $returnValues['_NewExchangeRunspace']
                }

                if ($null -ne $session)
                {
                    $session.Name = 'DSCExchangeSession'
                }

                New-Item -Path $DSCExchangeModulePath -Type Directory -Force | Out-Null
                Export-PSSession -Session $Session -OutputModule $DSCExchangeModulePath -Force -AllowClobber | Out-Null
                Import-Module -Name $DSCExchangeModulePath -Global -DisableNameChecking -Function *

                #Wait-Debugger
                $dbs = Get-MailboxDatabase
                Write-Host '----------------------------------------------------------------------------------------------------------------'
                'LogFolderPath:' | Write-Host
                $dbs[0].LogFolderPath | Out-String | Write-Host
                Write-Host '----------------------------------------------------------------------------------------------------------------'
            }
            PsDscRunAsCredential = $Creds
        }
    }
}
$configData = @{
    AllNodes = @(
        @{
            NodeName                    = "$env:COMPUTERNAME"
            PSDscAllowPlainTextPassword = $true
            PSDscAllowDomainUser        = $true
        }
    )
}

Remove-Item -Path C:\DSC\* -ErrorAction SilentlyContinue
mkdir -Path C:\DSC -ErrorAction SilentlyContinue

$cred = New-Object pscredential('contoso\install', ('Somepass1' | ConvertTo-SecureString -AsPlainText -Force))
ExchangeMailboxConfig -ConfigurationData $configData -Creds $cred -OutputPath C:\DSC

Set-DscLocalConfigurationManager C:\DSC -Verbose

Start-DscConfiguration C:\DSC -Force -Wait -Verbose

@johlju
Copy link
Member

johlju commented Nov 10, 2024

If anyone can explain why using PsDscRunAsCredential solves the problem, please enlighten us.

I would guess it is the same principle as a SecureString cannot be decrypted using another account that the one that encrypted it. Since it works now my guess is that the account used to sign the serialized payload must also be used de deserialize the payload. But doesn’t answer your question though, but great that it works and we got closer to understand the reason. 😊

@raandree
Copy link
Contributor

I would guess it is the same principle as a SecureString cannot be decrypted using another account that the one that encrypted it.

Why does the code run without problems in the context of the local system, the local administrator and any other administrator? Only when it is executed within the LCM does the serialization and deserialization not work.

@johlju
Copy link
Member

johlju commented Nov 11, 2024

You mean it does not work when LCM runs the resource using SYSTEM as it does default if the PsDscRunAsCredential is not passed, but it did work as specific user. So if it works when running the code in a session as SYSTEM and passing CONTOSO\Install credential to the function, then it sounds like a user right (or something) differ when used through LCM (a service) and running as SYSTEM. 🤔

@raandree
Copy link
Contributor

Correct, and since the LCM is a black box for us, I doubt that we can find the cause. Since DSC configurations don't work without credentials anyway, the workaround with PsDscRunAsCredential should be acceptable.

@gborus, how did your tests work out?

@gborus
Copy link
Contributor

gborus commented Nov 19, 2024

Sorry for the delay here, the small test configuration went through with the suggested modification. I'll make a bigger configuration change for this for wider test.
Thank you very much @raandree and @johlju for the suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug The issue is a bug. help wanted The issue is up for grabs for anyone in the community.
Projects
None yet
Development

No branches or pull requests

6 participants