powershelliisnullreferenceexception

Remove-IISConfigCollectionElement causes NullReferenceException


I am developing a PowerShell script to modify customHeaders in my website's web.config file, specifically the Content-Security-Policy header. I'm doing this with a script for 2 reasons:

  1. For my project, we have an automated tool to deploy all web services to IIS, so I don't want to add any manual steps.
  2. The values to go in the Content-Security-Policy are specific to the environment (we have different environments for dev, UAT, and production.)

I am using the IISAdministration module to do this, first by grabbing the existing values for Content-Security-Policy and modifying them within a variable internal to the script. Then, I attempt to remove the existing Content-Security-Policy header, then re-add it with my modified values. Please note that this script runs after the web service has been initially deployed, so it's modifying a site that already exists.

My script looks like this:

Import-Module IISAdministration

# got $env (the environment the script is being run in) earlier
if ($env -eq "dev) {
    $connectSrc = "; connect-src 'self' https://mydevurl.com"
} elseif ($env -eq "prod") {
    $connectSrc = "; connect-src 'self' https://myprodurl.com"
} else {
    $connectSrc = "; connect-src 'self' https://myuaturl.com"
}

$sectionPath = "system.webServer/httpProtocol"
$commitPath = "virtual_path_to_web_service"

$IISConfigSection = Get-IISConfigSection -SectionPath $sectionPath -CommitPath $commitPath
$IISConfigCollection = Get-IISConfigCollection -ConfigElement $IISConfigSection -CollectionName "customHeaders";

$Header = Get-IISConfigCollectionElement -ConfigCollection $IISConfigCollection -ConfigAttribute @{"name" = "Content-Security-Policy"}

if (!$Header) {
    Write-Error "No Content-Security-Policy currently exists."
} else {
    $existingPolicy = $Header.Attributes.value[1]  # successfully grabs the current value of the Content-Security-Policy configured in IIS
    $newPolicy = $existingPolicy + $connectSrc
    
    Remove-IISConfigCollectionElement  -ConfigCollection $IISConfigCollection -ConfigAttribute @{"name" = "Content-Security-Policy"}
    New-IISConfigCollectionElement -ConfigCollection $IISConfigCollection -ConfigAttribute @{"name"= "Content-Security-Policy"; "value" = $newPolicy;};
    $IIS = Get-IISServerManager
    $IIS.CommitChanges();
}

The script runs fine until we get to Remove-IISConfigCollectionElement. I see a print statement like this in my console:

[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):

But immediately it defaults to "Y", and then I get the following NullReferenceException:

Remove-IISConfigCollectionElement : Object reference not set to an instance of an object.
        At path\to\my\script.ps1:50 char:1
        + Remove-IISConfigCollectionElement  -ConfigCollection $IISConfigCollec ...
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Remove-IISConfigCollectionElement], NullReferenceException
        + FullyQualifiedErrorId : System.NullReferenceException,Microsoft.IIS.Powershell.Commands.RemoveIISConfigCollectio
        nElementCommand

I also captured a more detailed stack trace in a different test:

Exception             : System.NullReferenceException: Object reference not set to an instance of an object.
        at Microsoft.PowerShell.ConsoleHostUserInterface.PromptForChoice(String caption, String
        message, Collection`1 choices, Int32 defaultChoice)
        at
        System.Management.Automation.Internal.Host.InternalHostUserInterface.PromptForChoice(String
        caption, String message, Collection`1 choices, Int32 defaultChoice)
        at System.Management.Automation.MshCommandRuntime.InquireHelper(String inquireMessage,
        String inquireCaption, Boolean allowYesToAll, Boolean allowNoToAll, Boolean replaceNoWithHalt,
        Boolean hasSecurityImpact)
        at System.Management.Automation.MshCommandRuntime.DoShouldProcess(String
        verboseDescription, String verboseWarning, String caption, ShouldProcessReason&
        shouldProcessReason)
        at System.Management.Automation.MshCommandRuntime.ShouldProcess(String target)
        at System.Management.Automation.Cmdlet.ShouldProcess(String target)
        at Microsoft.IIS.Powershell.Commands.RemoveIISConfigCollectionElementCommand.ProcessRecord()
        at System.Management.Automation.CommandProcessor.ProcessRecord()
        TargetObject          :
        CategoryInfo          : NotSpecified: (:) [Remove-IISConfigCollectionElement], NullReferenceException
        FullyQualifiedErrorId : System.NullReferenceException,Microsoft.IIS.Powershell.Commands.RemoveIISConfigCollectionElemen
        tCommand
        ErrorDetails          :
        InvocationInfo        : System.Management.Automation.InvocationInfo
        ScriptStackTrace      : at <ScriptBlock>, path\to\my\script.ps1:
        line 45
        at <ScriptBlock>, <No file>: line 1
        PipelineIterationInfo : {}
        PSMessageDetails      :

I have also tested Remove-IISConfigCollectionElement by omitting the ConfigAttribute parameter, like this:

Remove-IISConfigCollectionElement  -ConfigCollection $IISConfigCollection

This results in the same error, so I think the error must be referring to $IISConfigCollection. But this doesn't make sense to me, because Get-IISConfigCollectionElement worked just fine a few lines earlier using $IISConfigCollection, and I was able to extract a value from it.

What is causing this error? Am I handling something wrong, or could there be an underlying bug with IIS? My IIS version is 10.0.17763.1. The only thing I can see from the stack trace is that there might be an error at Microsoft.PowerShell.ConsoleHostUserInterface.PromptForChoice, but to be honest, I don't even know why it's prompting, because I didn't include the -Confirm parameter when running Remove-IISConfigCollectionElement


Solution

  • The -Confirm parameter is prompting for confirmation per the ConfirmImpact value. More information can be found here.

    https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-shouldprocess?view=powershell-7.5#confirmimpact

    If you add -Confirm:$false to Remove-IISConfigCollectionElement that should suppress the prompt and allow the command to complete.