Framework/Core/FixControl/FixControlConfigResolver.ps1

Set-StrictMode -Version Latest

class FixControlConfigResolver: EventBase
{
    [string] $FolderPath = "";
    [string] $ConfigFilePath = "";
    [string] $InputFilePath = "";
    hidden [FixControlConfig[]] $FileContent = $null;

    [FixControlConfig[]] $FixControlResult = $null;

    [bool] $SubscriptionControls = $false;
    hidden [bool] $IsModified = $false;
    
    [string[]] $SubscriptionIds = @();
    [string[]] $ResourceGroupNames = @();
    [string[]] $ResourceTypes = @();
    [string[]] $ResourceTypeNames = @();
    [string[]] $ResourceNames = @();
    [string[]] $ControlIds = @();

    FixControlConfigResolver([string] $parameterFilePath, [string] $subscriptionIds, [string] $controlIds, [bool] $subscriptionControls)
    {
        $this.ParseParameterFile($parameterFilePath);
        $this.SubscriptionControls = $subscriptionControls;
        $this.SubscriptionIds += $this.ConvertToStringArray($subscriptionIds);
        $this.ControlIds += $this.ConvertToStringArray($controlIds);

        if($this.SubscriptionIds.Count -ne 0 -or $this.ControlIds.Count -ne 0)
        {
            # set the flag to true so that a copy of file will get generated
            $this.IsModified = $true;
        }
    }

    FixControlConfigResolver([string] $parameterFilePath, [string] $subscriptionIds, [string] $resourceGroupNames, [string] $resourceTypes, [string] $resourceTypeNames, [string] $resourceNames, [string] $controlIds)
    {
        $this.ParseParameterFile($parameterFilePath);
        $this.SubscriptionIds += $this.ConvertToStringArray($subscriptionIds);
        $this.ResourceGroupNames += $this.ConvertToStringArray($resourceGroupNames);
        $this.ResourceTypes += $this.ConvertToStringArray($resourceTypes);
        $this.ResourceTypeNames += $this.ConvertToStringArray($resourceTypeNames);
        $this.ResourceNames += $this.ConvertToStringArray($resourceNames);
        $this.ControlIds += $this.ConvertToStringArray($controlIds);

        if($this.SubscriptionIds.Count -ne 0 -or
            $this.ResourceGroupNames.Count -ne 0 -or
            $this.ResourceTypes.Count -ne 0 -or
            $this.ResourceTypeNames.Count -ne 0 -or
            $this.ResourceNames.Count -ne 0 -or
            $this.ControlIds.Count -ne 0)
        {
            # set the flag to true so that a copy of file will get generated
            $this.IsModified = $true;
        }
    }

    hidden [void] ParseParameterFile([string] $parameterFilePath)
    {
        if(-not [string]::IsNullOrEmpty($parameterFilePath))
        {
            $rawContent = @();
            if(Test-Path -Path $parameterFilePath)
            {
                $rawContent += (Get-Content -Raw -Path $parameterFilePath) | ConvertFrom-Json
            }
            else 
            {
                throw [SuppressedException] "Unable to find the parameter file [$parameterFilePath]";
            }

            if($rawContent.Count -ne 0)
            {
                $this.FileContent = @();
                $rawContent | ForEach-Object {
                    try
                    {
                        $this.FileContent += [FixControlConfig] $_;
                    }
                    catch
                    {
                        $this.PublishException($_);
                    }
                };
            }
        
            if(-not ($this.FileContent -and $this.FileContent.Count -ne 0))
            {
                throw [SuppressedException] "Parameter file [$parameterFilePath] is empty";
            }
            $this.FolderPath = [System.IO.Path]::GetDirectoryName($parameterFilePath) ;
            $this.InputFilePath = $parameterFilePath;
        }
        else
        {
            throw ([SuppressedException]::new(("The parameter 'ParameterFilePath' is null or empty."), [SuppressedExceptionType]::NullArgument))
        }
    }

    [FixControlConfig[]] GetFixControlParameters()
    {
        if(-not $this.FixControlResult)
        {    
            $this.PublishCustomMessage("Validating FixControl configuration file...");
            $this.FixControlResult = @();

            $this.FileContent | Where-Object { ($this.SubscriptionIds.Count -eq 0) -or ($this.SubscriptionIds -contains $_.SubscriptionContext.SubscriptionId) } | 
            ForEach-Object {
                $sub = $_;
                $subControls = @();
                $resourceGroups = @();
                
                if($this.SubscriptionControls)
                {
                    # Subscription controls
                    $subControls += $this.ProcessControls($sub.SubscriptionControls, $sub.SubscriptionContext, $null, $null);
                }
                else
                {
                    # Process Resources
                    $sub.ResourceGroups | Where-Object { ($this.ResourceGroupNames.Count -eq 0) -or ($this.ResourceGroupNames -contains $_.ResourceGroupName) } | 
                    ForEach-Object {
                        $resourceGroup = $_;
                        $resources = @();

                        $resourceGroup.Resources | 
                            Where-Object { ($this.ResourceTypes.Count -eq 0) -or ($this.ResourceTypes -contains $_.ResourceType) } | 
                            Where-Object { ($this.ResourceTypeNames.Count -eq 0) -or ($this.ResourceTypeNames -contains $_.ResourceTypeName) } | 
                            Where-Object { ($this.ResourceNames.Count -eq 0) -or ($this.ResourceNames -contains $_.ResourceName) } | 
                            ForEach-Object {
                                $resource = $_;
                                $controls = @();

                                if(-not [string]::IsNullOrWhiteSpace($resource.ResourceTypeName))
                                {
                                    $resource.ResourceTypeMapping = ([SVTMapping]::Mapping |
                                                Where-Object { $_.ResourceTypeName -eq $resource.ResourceTypeName } |
                                                Select-Object -First 1);
                                }

                                if($resource.ResourceTypeMapping)
                                {
                                    $controls += $this.ProcessControls($resource.Controls, $sub.SubscriptionContext, $resourceGroup, $resource);
                                
                                    if($controls.Count -ne 0)
                                    {
                                        $resource.Controls = @();
                                        $resource.Controls += $controls;
                                        $resources += $resource;
                                    }
                                }
                                else
                                {
                                    $this.PublishCustomMessage("The parameter 'ResourceTypeName' is invalid in file.`r`nNo fix will be applied for [Resource: $($resource.ResourceName)] [ResourceGroup: $($resourceGroup.ResourceGroupName)]", [MessageType]::Error);
                                }
                            };

                        if($resources.Count -ne 0)
                        {
                            $resourceGroup.Resources = @();
                            $resourceGroup.Resources += $resources;
                            $resourceGroups += $resourceGroup;
                        }
                    };
                }

                if($resourceGroups.Count -ne 0 -or $subControls.Count -ne 0)
                {
                    $sub.SubscriptionControls = @();
                    $sub.SubscriptionControls += $subControls;
                    $sub.ResourceGroups = @();
                    $sub.ResourceGroups += $resourceGroups;
                    $this.FixControlResult += $sub;
                }
            };

            if($this.FixControlResult.Count -eq 0)
            {
                throw ([SuppressedException]::new(("There are no controls to fix in the parameter file."), [SuppressedExceptionType]::InvalidOperation))
            }
            
            $this.PublishCustomMessage("Validation completed", [MessageType]::Update);

            if($this.IsModified)
            {
                $this.PublishCustomMessage("Saving the parameter file with the input values...");
                $this.ConfigFilePath = Join-Path $this.FolderPath ("FixControlConfig-" + $this.GenerateRunIdentifier() + ".json");
                [JsonHelper]::ConvertToJsonCustom($this.FixControlResult, 15, 15) | Out-File $this.ConfigFilePath
                $this.PublishCustomMessage("Parameter file has been saved to: '$($this.ConfigFilePath)'");
            }
            else
            {
                $this.ConfigFilePath = $this.InputFilePath;
            }
        }

        return $this.FixControlResult;
    }

    hidden [ControlParam[]] ProcessControls([ControlParam[]] $controls, [SubscriptionContext] $subContext, [ResourceGroupConfig] $resourceGroup, [ResourceConfig] $resource)
    {
        [ControlParam[]] $resultControls = @();

        if($controls -and $controls.Count -ne 0)
        {
            $controls | Where-Object { $_.Enabled -and (($this.ControlIds.Count -eq 0) -or ($this.ControlIds -contains $_.ControlID)) } |
            ForEach-Object {
                $control = $_;

                $printHeader = $true;
                $printFooter = $false;
                $control.ChildResourceParams |
                ForEach-Object {
                    $childParam = $_;

                    $nullParams = @();
                    if($childParam.Parameters)
                    {
                        $nullParams += [Helpers]::GetProperties($childParam.Parameters) | Where-Object { -not $childParam.Parameters.$_ };
                    }
                            
                    if($nullParams.Count -ne 0)
                    {
                        $message = "";
                        # Print header
                        if($printHeader)
                        {
                            $printHeader = $false;
                            $printFooter = $true;
                            
                            $message += [Constants]::DoubleDashLine;
                            $message += "`nSome additional values are required to fix the control"
                            $message += "`n" + [Constants]::SingleDashLine;
                            $message += "`nSubscription`t`t: $($subContext.SubscriptionName) [$($subContext.SubscriptionId)]";
                            if($resourceGroup)
                            {
                                $message += "`nResource Group`t`t: $($resourceGroup.ResourceGroupName)";
                                if($resource)
                                {
                                    $message += "`nResource Type Name`t: $($resource.ResourceTypeName)";
                                    $message += "`nResource Name`t`t: $($resource.ResourceName)";
                                }
                            }
                            $message += "`nControlId`t`t`t: $($control.ControlId)";
                            $message += "`nControlSeverity`t`t: $($control.ControlSeverity)";
                            $message += "`nDescription`t`t`t: $($control.Description)";
                        }
                        #else
                        #{
                        if(-not [string]::IsNullOrWhiteSpace($childParam.ChildResourceName))
                        {
                            if(-not [string]::IsNullOrWhiteSpace($message))
                            {
                                $message += "`n";
                            }
                            $message += "Child Resource Name`t`t: $($childParam.ChildResourceName)";
                        }
                        #}
                        $this.IsModified = $true;
                        $message += "`n`nPlease provide valid inputs for following..."
                        $this.PublishCustomMessage($message);

                        $nullParams | ForEach-Object {
                            $userValue = "";
                            while([string]::IsNullOrWhiteSpace($userValue))
                            {
                                $userValue = Read-Host "$_"
                                $userValue = $userValue.Trim();
                            }
                            $childParam.Parameters.$_ = $userValue;
                        };
                    }
                };
                if($printFooter)
                {
                    $this.PublishCustomMessage([Constants]::DoubleDashLine);
                }

                $resultControls += $control;    
            };
        }
        return $resultControls;
    }
}