cScom.psm1

enum ScomApprovalType
{
    Pending
    AutoReject
    AutoApprove
}

enum ScomEnsure
{
    Absent
    Present
}

enum ScomMaintenanceModeReason
{
    PlannedOther
    UnplannedOther
    PlannedHardwareMaintenance
    UnplannedHardwareMaintenance
    PlannedHardwareInstallation
    UnplannedHardwareInstallation
    PlannedOperatingSystemReconfiguration
    UnplannedOperatingSystemReconfiguration
    PlannedApplicationMaintenance
    UnplannedApplicationMaintenance
    ApplicationInstallation
    ApplicationUnresponsive
    ApplicationUnstable
    SecurityIssue
    LossOfNetworkConnectivity
}

class ScomReason
{
    [DscProperty()]
    [System.String]
    $Code

    [DscProperty()]
    [System.String]
    $Phrase
}

enum ScomReportSetting
{
    AutomaticallySend
    OptOut # Parameter name DoNotSend, whoever thought of this brilliant idea.
    PromptBeforeSending
}


enum ScomRole
{
    FirstManagementServer
    AdditionalManagementServer
    ReportServer
    WebConsole
    NativeConsole
}


[DscResource()]
class ScomAgentApprovalSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty(Mandatory)] [ScomApprovalType] $ApprovalType
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomAgentApprovalSetting] Get()
    {
        $reasonList = @()
        $setting = (Get-ScomAgentApprovalSetting).AgentApprovalSetting

        if ($setting -ne $this.ApprovalType)
        {
            $reasonList += @{
                Code   = 'ScomAgentApprovalSetting:ScomAgentApprovalSetting:WrongApprovalSetting'
                Phrase = "Approval setting is $setting but should be $($this.ApprovalType)"
            }
        }

        return @{
            IsSingleInstance = $this.IsSingleInstance
            ApprovalType     = $setting
            Reasons          = $reasonList
        }    
    }

    [void] Set()
    {
        $parameters = @{
            ErrorAction   = 'Stop'
            $this.ApprovalType = $true
            Confirm       = $false
        }
    
        Set-ScomAgentApprovalSetting @parameters      
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomAgentApprovalSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomAgentApprovalSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomAgentApprovalSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomAlertResolutionSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty()] [int] $AlertAutoResolveDays
    [DscProperty()] [int] $HealthyAlertAutoResolveDays
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomAlertResolutionSetting] Get()
    {
        $reasonList = @()
        $setting = Get-ScomAlertResolutionSetting

        if ($this.AlertAutoResolveDays -gt 0 -and $setting.AlertAutoResolveDays -ne $this.AlertAutoResolveDays)
        {
            $reasonList += @{
                Code   = 'ScomAlertResolutionSetting:ScomAlertResolutionSetting:WrongAutoResolveSetting'
                Phrase = "Auto resolve setting is $($setting.AlertAutoResolveDays) but should be $($this.AlertAutoResolveDays)"
            }
        }

        if ($this.HealthyAlertAutoResolveDays -gt 0 -and $setting.HealthyAlertAutoResolveDays -ne $this.HealthyAlertAutoResolveDays)
        {
            $reasonList += @{
                Code   = 'ScomAlertResolutionSetting:ScomAlertResolutionSetting:WrongHealthyAutoResolveSetting'
                Phrase = "Healthy auto resolve setting is $($setting.HealthyAlertAutoResolveDays) but should be $($this.HealthyAlertAutoResolveDays)"
            }
        }

        return @{
            IsSingleInstance            = $this.IsSingleInstance
            AlertAutoResolveDays        = $setting.AlertAutoResolveDays
            HealthyAlertAutoResolveDays = $setting.HealthyAlertAutoResolveDays
            Reasons                     = $reasonList
        }      
    }

    [void] Set()
    {
        $parameters = @{
            ErrorAction = 'Stop'
        }
    
        if ($this.AlertAutoResolveDays -le 0 -and $this.HealthyAlertAutoResolveDays -le 0)
        {
            return
        }
    
        if ($this.AlertAutoResolveDays -gt 0)
        {
            $parameters['AlertAutoResolveDays'] = $this.AlertAutoResolveDays
        }
    
        if ($this.HealthyAlertAutoResolveDays -gt 0)
        {
            $parameters['HealthyAlertAutoResolveDays'] = $this.HealthyAlertAutoResolveDays
        }
    
        Set-ScomAlertResolutionSetting @parameters     
    }

    [bool] Test()
    {    
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomAlertResolutionSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomAlertResolutionSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomAlertResolutionSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomComponent
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty(Key)] [ScomRole] $Role
    [DscProperty(Mandatory)] [string] $SourcePath
    [DscProperty()] [System.String] $ManagementServer
    [DscProperty()] [System.String] $ManagementGroupName
    [DscProperty()] [System.Management.Automation.PSCredential] $DataReader
    [DscProperty()] [System.Management.Automation.PSCredential] $DataWriter
    [DscProperty()] [System.String] $SqlServerInstance
    [DscProperty()] [uint16] $SqlInstancePort
    [DscProperty()] [uint16] $DwSqlInstancePort
    [DscProperty()] [System.String] $DwSqlServerInstance
    [DscProperty()] [ScomEnsure] $Ensure
    [DscProperty()] [System.String] $ProductKey
    [DscProperty()] [System.String] $InstallLocation
    [DscProperty()] [System.UInt16] $ManagementServicePort
    [DscProperty()] [System.Management.Automation.PSCredential] $ActionAccount
    [DscProperty()] [System.Management.Automation.PSCredential] $DASAccount
    [DscProperty()] [System.String] $DatabaseName
    [DscProperty()] [System.String] $DwDatabaseName
    [DscProperty()] [string] $WebSiteName
    [DscProperty()] [string] $WebConsoleAuthorizationMode
    [DscProperty()] [bool] $WebConsoleUseSSL
    [DscProperty()] [bool] $UseMicrosoftUpdate
    [DscProperty()] [string] $SRSInstance
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    ScomComponent ()
    {
        $this.IsSingleInstance = 'yes'
        $this.InstallLocation = 'C:\Program Files\Microsoft System Center\Operations Manager'
        $this.Ensure = 'Present'
        $this.DatabaseName = "OperationsManager"
        $this.DwDatabaseName = "OperationsManagerDW"
        $this.WebSiteName = 'Default Web Site'
        $this.WebConsoleAuthorizationMode = 'Mixed'
        $this.WebConsoleUseSSL = $false
        $this.UseMicrosoftUpdate = $true
        $this.SqlInstancePort = 1433
        $this.DwSqlInstancePort = 1433
        $this.ManagementServicePort = 5723
    }

    [ScomComponent] Get()
    {
        $status = Test-cScomInstallationStatus -ScomComponent $this

        $returnTable = @{
            IsSingleInstance            = $this.IsSingleInstance
            Role                        = $this.Role
            SourcePath                  = $this.SourcePath
            ManagementServer            = $this.ManagementServer
            ManagementGroupName         = $this.ManagementGroupName
            DataReader                  = $this.DataReader
            DataWriter                  = $this.DataWriter
            SqlServerInstance           = $this.SqlServerInstance
            SqlInstancePort             = $this.SqlInstancePort
            DwSqlInstancePort           = $this.DwSqlInstancePort
            DwSqlServerInstance         = $this.DwSqlServerInstance
            Ensure                      = $this.Ensure
            ProductKey                  = $this.ProductKey
            InstallLocation             = $this.InstallLocation
            ManagementServicePort       = $this.ManagementServicePort
            ActionAccount               = $this.ActionAccount
            DASAccount                  = $this.DASAccount
            DatabaseName                = $this.DatabaseName
            DwDatabaseName              = $this.DwDatabaseName
            WebSiteName                 = $this.WebSiteName
            WebConsoleAuthorizationMode = $this.WebConsoleAuthorizationMode
            WebConsoleUseSSL            = $this.WebConsoleUseSSL
            UseMicrosoftUpdate          = $this.UseMicrosoftUpdate
            SRSInstance                 = $this.SRSInstance
        }
    
        if (-not $status -and $this.Ensure -eq 'Present')
        {
            $returnTable.Reasons = @(
                [ScomReason]@{
                    Code   = 'ScomComponent:ScomComponent:ProductNotInstalled'
                    Phrase = "SCOM component $($this.Role) is not installed, but it should be."
                }
            )
        }
    
        if ($status -and $this.Ensure -eq 'Absent')
        {
            $returnTable.Reasons = @(
                [ScomReason]@{
                    Code   = 'ScomComponent:ScomComponent:ProductInstalled'
                    Phrase = "SCOM component $($this.Role) is installed, but it should not be."
                }
            )
        }
    
        return $returnTable      
    }

    [void] Set()
    {
        $parameters = @{
            Role = $this.Role
        }
    
        switch ($this.Role)
        {
            'FirstManagementServer'
            {
                $parameters['DwDatabaseName'] = $this.DwDatabaseName
                $parameters['DwSqlInstancePort'] = $this.DwSqlInstancePort
                $parameters['DwSqlServerInstance'] = $this.DwSqlServerInstance
                $parameters['ManagementGroupName'] = $this.ManagementGroupName
                $parameters['ActionAccountPassword'] = $this.ActionAccount.GetNetworkCredential().Password
                $parameters['ActionAccountUser'] = $this.ActionAccount.UserName
                $parameters['DASAccountPassword'] = $this.DASAccount.GetNetworkCredential().Password
                $parameters['DASAccountUser'] = $this.DASAccount.UserName
                $parameters['DatabaseName'] = $this.DatabaseName
                $parameters['DataReaderPassword'] = $this.DataReader.GetNetworkCredential().Password
                $parameters['DataReaderUser'] = $this.DataReader.UserName
                $parameters['DataWriterPassword'] = $this.DataWriter.GetNetworkCredential().Password
                $parameters['DataWriterUser'] = $this.DataWriter.UserName
                $parameters['InstallLocation'] = $this.InstallLocation
                $parameters['ManagementServicePort'] = $this.ManagementServicePort
                $parameters['SqlInstancePort'] = $this.SqlInstancePort
                $parameters['SqlServerInstance'] = $this.SqlServerInstance
            }
            'AdditionalManagementServer'
            {
                $parameters['ActionAccountPassword'] = $this.ActionAccount.GetNetworkCredential().Password
                $parameters['ActionAccountUser'] = $this.ActionAccount.UserName
                $parameters['DASAccountPassword'] = $this.DASAccount.GetNetworkCredential().Password
                $parameters['DASAccountUser'] = $this.DASAccount.UserName
                $parameters['DatabaseName'] = $this.DatabaseName
                $parameters['DataReaderPassword'] = $this.DataReader.GetNetworkCredential().Password
                $parameters['DataReaderUser'] = $this.DataReader.UserName
                $parameters['DataWriterPassword'] = $this.DataWriter.GetNetworkCredential().Password
                $parameters['DataWriterUser'] = $this.DataWriter.UserName
                $parameters['InstallLocation'] = $this.InstallLocation
                $parameters['ManagementServicePort'] = $this.ManagementServicePort
                $parameters['SqlInstancePort'] = $this.SqlInstancePort
                $parameters['SqlServerInstance'] = $this.SqlServerInstance
            }
            'ReportServer'
            {
                $parameters['ManagementServer'] = $this.ManagementServer
                $parameters['SRSInstance'] = $this.SRSInstance
                $parameters['DataReaderUser'] = $this.DataReader.UserName
                $parameters['DataReaderPassword'] = $this.DataReader.GetNetworkCredential().Password
            }
            'WebConsole'
            {
                $parameters['WebSiteName'] = $this.WebSiteName
                $parameters['ManagementServer'] = $this.ManagementServer
                $parameters['WebConsoleAuthorizationMode'] = $this.WebConsoleAuthorizationMode
            }
            'NativeConsole'
            {
                $parameters['InstallLocation'] = $this.InstallLocation
            }
        }
    
        $commandline = Get-cScomParameter @parameters -Uninstall:$($this.Ensure -eq 'Absent')
        $setupEchse = Get-ChildItem -Path $this.SourcePath -Filter setup.exe
    
        if (-not $setupEchse)
        {
            Write-Error -Message "Path $($this.SourcePath) is missing setup.exe"
            return
        }
    
        $obfuscatedCmdline = $commandline
        foreach ($pwdKey in $parameters.GetEnumerator())
        {
            if ($pwdKey.Key -notlike '*Password') { continue }
            $obfuscatedCmdline = $obfuscatedCmdline.Replace($pwdKey.Value, '******')
        }
        Write-Verbose -Message "Starting setup of SCOM $($this.Role): $($setupEchse.FullName) $obfuscatedCmdline"
        $installation = Start-Process -Wait -PassThru -FilePath $setupEchse.FullName -ArgumentList $commandLine -WindowStyle Hidden
    
        if ($installation.ExitCode -eq 3010) { $global:DSCMachineStatus = 1; return }
    
        if ($installation.ExitCode -ne 0)
        {
            Write-Error -Message "Installation ran into an error. Exit code was $($installation.ExitCode)"
        }    
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomComponent].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomComponent].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomComponent].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomDatabaseGroomingSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty()] [byte] $AlertDaysToKeep
    [DscProperty()] [byte] $AvailabilityHistoryDaysToKeep
    [DscProperty()] [byte] $EventDaysToKeep
    [DscProperty()] [byte] $JobStatusDaysToKeep
    [DscProperty()] [byte] $MaintenanceModeHistoryDaysToKeep
    [DscProperty()] [byte] $MonitoringJobDaysToKeep
    [DscProperty()] [byte] $PerformanceDataDaysToKeep
    [DscProperty()] [byte] $PerformanceSignatureDaysToKeep
    [DscProperty()] [byte] $StateChangeEventDaysToKeep
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomDatabaseGroomingSetting] Get()
    {
        $reasonList = @()
        $setting = Get-ScomDatabaseGroomingSetting
    
        foreach ($parameter in $this.GetConfigurableDscProperties().GetEnumerator())
        {
            if ($parameter.Key -notlike '*Keep') { continue }
            $settingName = $parameter.Key
            
            if ($setting.$settingName -ne $parameter.value)
            {
                $reasonList += @{
                    Code   = "ScomDatabaseGroomingSetting:ScomDatabaseGroomingSetting:Wrong$($settingName)Setting"
                    Phrase = "Setting '$settingName' is $($setting.$settingName) but should be $($parameter.value)"
                }
            }
        }
    
        return @{
            IsSingleInstance                 = $this.IsSingleInstance
            AlertDaysToKeep                  = $setting.AlertDaysToKeep
            AvailabilityHistoryDaysToKeep    = $setting.AvailabilityHistoryDaysToKeep
            EventDaysToKeep                  = $setting.EventDaysToKeep
            JobStatusDaysToKeep              = $setting.JobStatusDaysToKeep
            MaintenanceModeHistoryDaysToKeep = $setting.MaintenanceModeHistoryDaysToKeep
            MonitoringJobDaysToKeep          = $setting.MonitoringJobDaysToKeep
            PerformanceDataDaysToKeep        = $setting.PerformanceDataDaysToKeep
            PerformanceSignatureDaysToKeep   = $setting.PerformanceSignatureDaysToKeep
            StateChangeEventDaysToKeep       = $setting.StateChangeEventDaysToKeep
            Reasons                          = $reasonList
        }      
    }

    [void] Set()
    {
        $parameters = $this.GetConfigurableDscProperties()
    
        Set-ScomDatabaseGroomingSetting @parameters  
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomDatabaseGroomingSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomDatabaseGroomingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomDatabaseGroomingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomDataWarehouseSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty(Mandatory)] [string] $DatabaseName
    [DscProperty(Mandatory)] [string] $ServerName
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomDataWarehouseSetting] Get()
    {

        $reasonList = @()
        $setting = Get-ScomDataWarehouseSetting
    
        if ($setting.DataWarehouseServerName -ne $this.ServerName)
        {
            $reasonList += @{
                Code   = 'ScomDataWarehouseSetting:ScomDataWarehouseSetting:WrongServerName'
                Phrase = "Approval setting is $($setting.DataWarehouseServerName) but should be $($this.ServerName)"
            }
        }
    
        if ($setting.DataWarehouseDatabaseName -ne $this.DatabaseName)
        {
            $reasonList += @{
                Code   = 'ScomDataWarehouseSetting:ScomDataWarehouseSetting:WrongDatabaseName'
                Phrase = "Approval setting is $($setting.DataWarehouseDatabaseName) but should be $($this.DatabaseName)"
            }
        }
    
        return @{
            IsSingleInstance = $this.IsSingleInstance
            DatabaseName     = $setting.DataWarehouseDatabaseName
            ServerName       = $setting.DataWarehouseServerName
            Reasons          = $reasonList
        } 
    }

    [void] Set()
    {

        $parameters = @{
            ErrorAction  = 'Stop'
            DatabaseName = $this.DatabaseName
            ServerName   = $this.ServerName
        }
    
        Set-ScomDataWarehouseSetting @parameters   
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomDataWarehouseSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomDataWarehouseSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomDataWarehouseSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomDiscovery
{
    [DscProperty(Key)] [string] $Discovery
    [DscProperty(Key)] [string] $ManagementPack
    [DscProperty()] [string[]] $ClassName
    [DscProperty()] [string[]] $GroupOrInstance
    [DscProperty()] [bool] $Enforce
    [DscProperty()] [ScomEnsure] $Ensure
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons
   
    ScomDiscovery ()
    {
        $this.Ensure = 'Present'
        $this.Enforce = $true
    }

    [ScomDiscovery] Get()
    {
        $manPack = Get-ScomManagementPack | Where-Object { -not $_.Sealed -and ($_.DisplayName -eq $this.ManagementPack -or $_.Name -eq $this.ManagementPack) }
        $disco = Get-SCDiscovery | Where-Object { $_.DisplayName -eq $this.Discovery -or $_.Name -eq $this.Discovery }
    
        $reasonList = @()
    
        if ($this.Ensure -eq 'Present' -and -not $manPack)
        {
            $reasonList += @{
                Code   = 'ScomDiscovery:ScomDiscovery:NoManagementPack'
                Phrase = "No management pack called $($this.ManagementPack) found. Is it maybe sealed?"
            }
        }
    
        if ($this.Ensure -eq 'Present' -and -not $disco)
        {
            $reasonList += @{
                Code   = 'ScomDiscovery:ScomDiscovery:NoDiscovery'
                Phrase = "No discovery called $($this.Discovery) found."
            }
        }
       
        if ($this.Ensure -eq 'Absent' -and $disco.Enabled)
        {
            $reasonList += @{
                Code   = 'ScomDiscovery:ScomDiscovery:DiscoveryConfigured'
                Phrase = "Discovery $($this.Name) is enabled, should be disabled. Discovery ID $($disco.Id)"
            }
        }
       
        if ($this.Ensure -eq 'Present' -and -not $disco.Enabled)
        {
            $reasonList += @{
                Code   = 'ScomDiscovery:ScomDiscovery:DiscoveryNotConfigured'
                Phrase = "Discovery $($this.Name) is disabled, should be enabled."
            }
        }
    
       
        return @{
            Discovery       = $disco.Name
            ManagementPack  = $manPack.Name
            Class           = $this.ClassName
            GroupOrInstance = $this.GroupOrInstance
            Enforce         = $this.Enforce
            Reasons         = $reasonList
        }      
    }

    [void] Set()
    {
        $manPack = Get-ScomManagementPack | Where-Object { -not $_.Sealed -and ($_.DisplayName -eq $this.ManagementPack -or $_.Name -eq $this.ManagementPack) }
        $disco = Get-SCDiscovery | Where-Object { $_.DisplayName -eq $this.Discovery -or $_.Name -eq $this.Discovery }
    
        if (-not $manPack)
        {
            Write-Error -Message "No management pack called $($this.ManagementPack) found. Is it maybe sealed?"
            return
        }
    
        if (-not $disco)
        {
            Write-Error -Message "No discovery called $($this.Discovery) found."
            return
        }
    
        $parameters = @{
            ManagementPack = $manPack
            Discovery      = $disco
            Enforce        = $this.Enforce
        }
        
        if ($this.ClassName)
        {
            $scomClass = Get-ScomClass | Where-Object { $_.DisplayName -in $this.ClassName -or $_.Name -in $this.ClassName }
            if (-not $scomClass) { Write-Error -Message "No class(es) called $($this.ClassName) found."; return }
    
            $parameters['Class'] = $scomClass
        }
        elseif ($this.GroupOrInstance)
        {
            $scomInstance = Get-ScomClassInstance | Where-Object DisplayName -in $this.GroupOrInstance
            if (-not $scomInstance) { Write-Error -Message "No class instance(s) or group(s) called $($this.GroupOrInstance) found."; return }
    
            $parameters['Instance'] = $this.ClassName
        }
    
        if ($this.Ensure -eq 'Present')
        {
            Enable-ScomDiscovery @parameters
        }
    
        if ($this.Ensure -eq 'Absent')
        {
            Disable-ScomDiscovery @parameters
        }     
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomDiscovery].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomDiscovery].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomDiscovery].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomErrorReportingSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty(Mandatory)] [ScomReportSetting] $ReportSetting
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomErrorReportingSetting] Get()
    {
        $reasonList = @()
        $setting = (Get-ScomErrorReportingSetting).ErrorReportingSetting
    
        if ($setting -ne $this.ReportSetting)
        {
            $reasonList += @{
                Code   = 'ScomErrorReportingSetting:ScomErrorReportingSetting:WrongApprovalSetting'
                Phrase = "Approval setting is $setting but should be $($this.ReportSetting)"
            }
        }
    
        return @{
            IsSingleInstance = $this.IsSingleInstance
            ReportSetting    = $setting
            Reasons          = $reasonList
        }     
    }

    [void] Set()
    {
        $parameters = @{
            ErrorAction    = 'Stop'
            $this.ReportSetting = $true
            Confirm        = $false
        }
    
        if ($parameters.Contains('OptOut'))
        {
            $parameters.Remove('OptOut')
            $parameters['DoNotSend'] = $true
        }
        Set-ScomErrorReportingSetting @parameters
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomErrorReportingSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomErrorReportingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomErrorReportingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomHeartbeatSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty()] [int] $MissingHeartbeatThreshold
    [DscProperty()] [int] $HeartbeatIntervalSeconds
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomHeartbeatSetting] Get()
    {
        $reasonList = @()
        $setting = Get-ScomHeartbeatSetting
    
        if ($this.HeartbeatIntervalSeconds -gt 0 -and $setting.AgentHeartbeatInterval -ne $this.HeartbeatIntervalSeconds)
        {
            $reasonList += @{
                Code   = 'ScomHeartbeatSetting:ScomHeartbeatSetting:WrongHeartbeatIntervalSetting'
                Phrase = "Heartbeat Interval setting is $($setting.AgentHeartbeatInterval) but should be $($this.HeartbeatIntervalSeconds)"
            }
        }
    
        if ($this.MissingHeartbeatThreshold -gt 0 -and $setting.MissingHeartbeatThreshold -ne $this.MissingHeartbeatThreshold)
        {
            $reasonList += @{
                Code   = 'ScomHeartbeatSetting:ScomHeartbeatSetting:WrongThresholdSetting'
                Phrase = "Missing Heartbeat Threshold setting is $($setting.MissingHeartbeatThreshold) but should be $this.MissingHeartbeatThreshold"
            }
        }
    
        return @{
            IsSingleInstance          = $this.IsSingleInstance
            MissingHeartbeatThreshold = $setting.MissingHeartbeatThreshold
            HeartbeatIntervalSeconds  = $setting.AgentHeartbeatInterval
            Reasons                   = $reasonList
        }      
    }

    [void] Set()
    {
        $parameters = @{
            ErrorAction = 'Stop'
            Confirm     = $false
        }
    
        if ($this.MissingHeartbeatThreshold -gt 0) { $parameters['MissingHeartbeatThreshold'] = $this.MissingHeartbeatThreshold }
        if ($this.HeartbeatIntervalSeconds -gt 0) { $parameters['HeartbeatInterval'] = New-TimeSpan -Seconds $this.HeartbeatInterval }
    
        Set-ScomHeartbeatSetting @parameters
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomHeartbeatSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomHeartbeatSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomHeartbeatSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomMaintenanceSchedule
{
    [DscProperty(Key)] [string] $Name
    [DscProperty(Mandatory)] [string[]] $MonitoringObjectGuid
    [DscProperty(Mandatory)] [datetime] $ActiveStartTime
    [DscProperty(Mandatory)] [uint32] $Duration
    [DscProperty(Mandatory)] [ScomMaintenanceModeReason] $ReasonCode
    [DscProperty(Mandatory)] [uint32] $FreqType
    [DscProperty()] [bool] $Recursive
    [DscProperty()] [ScomEnsure] $Ensure
    [DscProperty()] [datetime] $ActiveEndDate
    [DscProperty()] [string] $Comments
    [DscProperty()] [uint32] $FreqInterval
    [DscProperty()] [uint32] $FreqRecurrenceFactor
    [DscProperty()] [uint32] $FreqRelativeInterval
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    ScomMaintenanceSchedule ()
    {
        $this.Ensure = 'Present'
        $this.Recursive = $false
    }

    [ScomMaintenanceSchedule] Get()
    {
        $schedule = Get-ScomMaintenanceScheduleList | Where-Object -Property Name -eq $this.Name | Get-ScomMaintenanceSchedule
        $reasonList = @()
    
        if ($this.Ensure -eq 'Absent' -and $null -ne $schedule)
        {
            $reasonList += @{
                Code   = 'ScomMaintenanceSchedule:ScomMaintenanceSchedule:SchedulePresent'
                Phrase = "Maintenance schedule $($this.Name) is present, should be absent. Schedule ID $($schedule.Id)"
            }
        }
    
        if ($this.Ensure -eq 'Present' -and $null -eq $schedule)
        {
            $reasonList += @{
                Code   = 'ScomMaintenanceSchedule:ScomMaintenanceSchedule:ScheduleAbsent'
                Phrase = "Maintenance schedule $($this.Name) is absent, should be present."
            }
        }
    
        # Check other properties
    
        return @{
            Reasons = $reasonList
        }     
    }

    [void] Set()
    {    
        $schedule = Get-ScomMaintenanceScheduleList | Where-Object -Property Name -eq $this.Name | Get-ScomMaintenanceSchedule
    
        if ($this.Ensure -eq 'Present' -and $schedule)
        {
            $parameters = Sync-Parameter -Parameters $this.GetConfigurableDscProperties() -Command (Get-Command -Name Edit-ScomMaintenanceSchedule)
            Edit-ScomMaintenanceSchedule @parameters -Id $schedule.Id
        }
        elseif ($this.Ensure -eq 'Present')
        {
                
            $parameters = Sync-Parameter -Parameters $this.GetConfigurableDscProperties() -Command (Get-Command -Name New-ScomMaintenanceSchedule)
            New-ScomMaintenanceSchedule @parameters
        }
        else
        {
            $schedule | Remove-ScomMaintenanceSchedule -Confirm:$false
        }  
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomMaintenanceSchedule].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomMaintenanceSchedule].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomMaintenanceSchedule].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomManagementPack
{
    [DscProperty(Key)] [System.String] $Name
    [DscProperty()] [System.String] $ManagementPackPath
    [DscProperty()] [System.String] $ManagementPackContent
    [DscProperty()] [ScomEnsure] $Ensure
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    ScomManagementPack ()
    {
        $this.Ensure = 'Present'
    }

    [ScomManagementPack] Get()
    {
        $reasonList = @()
        $mp = Get-SCManagementPack -Name $this.Name
    
        if ($null -eq $mp -and $this.Ensure -eq 'Present')
        {
            $reasonList += @{
                Code   = 'ScomManagementPack:ScomManagementPack:ManagementPackNotFound'
                Phrase = "No management pack with the name $($this.Name) was found."
            }
        }
    
        if ($null -ne $mp -and $this.Ensure -eq 'Absent')
        {
            $reasonList += @{
                Code   = 'ScomManagementPack:ScomManagementPack:TooManyManagementPacks'
                Phrase = "A management pack with the name $($this.Name) was found but ensure is set to absent."
            }
        }
    
        return @{
            Name                  = $mp.Name
            ManagementPackPath    = $this.ManagementPackPath
            ManagementPackContent = $this.ManagementPackContent
            Ensure                = $this.Ensure
            Reasons               = $reasonList
        }       
    }

    [void] Set()
    {
        if ($this.Ensure -eq 'Absent')
        {
            Get-SCManagementPack -Name $this.Name | Remove-SCManagementPack
            return
        }
    
        if ($this.ManagementPackContent -and $this.ManagementPackPath)
        {
            throw ([ArgumentException]::new('You cannot use ManagementPackContent and ManagementPackPath at the same time.'))
        }
    
        if ($this.ManagementPackPath -and -not (Test-Path -Path $this.ManagementPackPath))
        {
            throw ([IO.FileNotFoundException]::new("$($this.ManagementPackPath) was not found."))
        }
    
        if ((Get-Item -Path $this.ManagementPackPath).Extension -notin '.xml', '.mp', '.mpb')
        {
            throw ([ArgumentException]::new("Invalid management pack extension. '$((Get-Item -Path $this.ManagementPackPath).Extension)' not in .xml,.mp,.mpb"))
        }
    
        if ($this.ManagementPackContent)
        {
            $tmpPath = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath "$($this.Name).xml"
            $this.ManagementPackPath = (New-Item -ItemType File -Path $tmpPath -Force).FullName
            Set-Content -Path $tmpPath -Force -Encoding Unicode -Value $this.ManagementPackContent
        }
    
        if ($this.ManagementPackPath)
        {
            Import-SCManagementPack -FullName $this.ManagementPackPath
        }    
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomManagementPack].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomManagementPack].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomManagementPack].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomReportingSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty(Mandatory)] [string] $ReportingServerUrl
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomReportingSetting] Get()
    {
        $reasonList = @()
        $setting = (Get-ScomReportingSetting).ReportingServerUrl
    
        if ($setting -ne $this.ReportingServerUrl)
        {
            $reasonList += @{
                Code   = 'ScomReportingSetting:ScomReportingSetting:WrongReportingServerUrlSetting'
                Phrase = "Reporting Server Url setting is $setting but should be $($this.ReportingServerUrl)"
            }
        }
    
        return @{
            IsSingleInstance   = $this.IsSingleInstance
            ReportingServerUrl = $setting
            Reasons            = $reasonList
        }    
    }

    [void] Set()
    {
        $parameters = @{
            ErrorAction        = 'Stop'
            ReportingServerUrl = $this.ReportingServerUrl
            Confirm            = $false
        }
    
        Set-ScomReportingSetting @parameters    
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomReportingSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomReportingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomReportingSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

[DscResource()]
class ScomWebAddressSetting
{
    [DscProperty(Key)] [ValidateSet('yes')] [string] $IsSingleInstance
    [DscProperty()] [string] $WebConsoleUrl
    [DscProperty()] [string] $OnlineProductKnowledgeUrl
    [DscProperty(NotConfigurable)] [ScomReason[]] $Reasons

    [ScomWebAddressSetting] Get()
    {
        $reasonList = @()
        $setting = Get-ScomWebAddressSetting
    
        if (-not [string]::IsNullOrWhiteSpace($this.WebConsoleUrl) -and $setting.WebConsoleUrl -ne $this.WebConsoleUrl)
        {
            $reasonList += @{
                Code   = 'ScomWebAddressSetting:ScomWebAddressSetting:WrongWebUrlSetting'
                Phrase = "Web Console Url is $($setting.WebConsoleUrl) but should be $this.WebConsoleUrl"
            }
        }
    
        if (-not [string]::IsNullOrWhiteSpace($this.OnlineProductKnowledgeUrl) -and $setting.OnlineProductKnowledgeUrl -ne $this.OnlineProductKnowledgeUrl)
        {
            $reasonList += @{
                Code   = 'ScomWebAddressSetting:ScomWebAddressSetting:WrongKnowledgeUrletting'
                Phrase = "Online Product Knowledge Url is $($setting.OnlineProductKnowledgeUrl) but should be $this.OnlineProductKnowledgeUrl"
            }
        }
    
        return @{
            IsSingleInstance          = $this.IsSingleInstance
            WebConsoleUrl             = $setting.WebConsoleUrl 
            OnlineProductKnowledgeUrl = $setting.OnlineProductKnowledgeUrl 
            Reasons                   = $reasonList
        }  
    }

    [void] Set()
    {

        $parameters = @{
            ErrorAction = 'Stop'
            Confirm     = $false
        }
    
        if ($this.WebConsoleUrl) { $parameters['WebConsoleUrl'] = $this.WebConsoleUrl }
        if ($this.OnlineProductKnowledgeUrl) { $parameters['OnlineProductKnowledgeUrl'] = $this.OnlineProductKnowledgeUrl }
    
        Set-ScomWebAddressSetting @parameters 
    }

    [bool] Test()
    {
        return ($this.Get().Reasons.Count -eq 0)
    }

    [Hashtable] GetConfigurableDscProperties()
    {
        # This method returns a hashtable of properties with two special workarounds
        # The hashtable will not include any properties marked as "NotConfigurable"
        # Any properties with a ValidateSet of "True","False" will beconverted to Boolean type
        # The intent is to simplify splatting to functions
        # Source: https://gist.github.com/mgreenegit/e3a9b4e136fc2d510cf87e20390daa44
        $DscProperties = @{}
        foreach ($property in [ScomWebAddressSetting].GetProperties().Name)
        {
            # Checks if "NotConfigurable" attribute is set
            $notConfigurable = [ScomWebAddressSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.DscPropertyAttribute] }).NotConfigurable
            if (!$notConfigurable)
            {
                $value = $this.$property
                # Gets the list of valid values from the ValidateSet attribute
                $validateSet = [ScomWebAddressSetting].GetProperty($property).GetCustomAttributes($false).Where({ $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues
                if ($validateSet)
                {
                    # Workaround for boolean types
                    if ($null -eq (Compare-Object @('True', 'False') $validateSet))
                    {
                        $value = [System.Convert]::ToBoolean($this.$property)
                    }
                }
                # Add property to new
                $DscProperties.add($property, $value)
            } 
        }
        return $DscProperties
    }
}

<#
.SYNOPSIS
    Return a formatted command line to install SCOM
.DESCRIPTION
    Return a formatted command line to install SCOM
.EXAMPLE
    Get-cScomParameter @parameters
 
    /install /silent /components:OMConsole /AcceptEndUserLicenseAgreement:"1" /EnableErrorReporting:"Never" /InstallLocation:"C:\Program Files\Microsoft System Center\Operations Manager" /SendCEIPReports:"0" /UseMicrosoftUpdate:"0"
#>

function Get-cScomParameter
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PsAvoidUsingPlaintextForPassword", "", Justification = "Nope")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "", Justification = "Nope")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification = "Parameters used programmatically")]
    [OutputType([string])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ScomRole]
        $Role,

        [string]
        $ManagementGroupName,

        [uint16]
        $ManagementServicePort,

        [string]
        $SqlServerInstance,

        [string]
        $SqlInstancePort,

        [string]
        $DatabaseName,

        [string]
        $DwSqlServerInstance,

        [string]
        $InstallLocation,

        [string]
        $DwSqlInstancePort,
        [string]
        $DwDatabaseName,
        [string]
        $ActionAccountUser,
        [string]
        $ActionAccountPassword,
        [string]
        $DASAccountUser,
        [string]
        $DASAccountPassword,
        [string]
        $DataReaderUser,
        [string]
        $DataReaderPassword,
        [string]
        $DataWriterUser,
        [string]
        $DataWriterPassword,
        [string]
        $ManagementServer,
        [string]
        $WebSiteName,
        [string]
        $WebConsoleAuthorizationMode,
        [uint16]
        $WebConsoleUseSSL,
        [string]
        $SRSInstance,
        [uint16]
        $UseMicrosoftUpdate = 0,
        [switch]
        $Uninstall
    )

    $parameters = @{
        FirstManagementServer      = @{
            ManagementGroupName           = 'SCOM2019'
            ManagementServicePort         = '5723'
            SqlServerInstance             = ''
            SqlInstancePort               = '1433'
            DatabaseName                  = 'OperationsManager'
            DwSqlServerInstance           = ''
            InstallLocation               = 'C:\Program Files\Microsoft System Center\Operations Manager'
            DwSqlInstancePort             = '1433'
            DwDatabaseName                = 'OperationsManagerDW'
            ActionAccountUser             = 'OM19AA'
            ActionAccountPassword         = ''
            DASAccountUser                = 'OM19DAS' 
            DASAccountPassword            = ''
            DataReaderUser                = 'OM19READ'
            DataReaderPassword            = ''
            DataWriterUser                = 'OM19WRITE'
            DataWriterPassword            = ''
            EnableErrorReporting          = 'Never'
            SendCEIPReports               = '0'
            UseMicrosoftUpdate            = '0'
            AcceptEndUserLicenseAgreement = '1'     
        }

        AdditionalManagementServer = @{
            SqlServerInstance             = ''
            SqlInstancePort               = '1433'
            ManagementServicePort         = '5723'
            DatabaseName                  = 'OperationsManager'
            InstallLocation               = 'C:\Program Files\Microsoft System Center\Operations Manager'
            ActionAccountUser             = 'OM19AA'
            ActionAccountPassword         = ''
            DASAccountUser                = 'OM19DAS' 
            DASAccountPassword            = ''
            DataReaderUser                = 'OM19READ'
            DataReaderPassword            = ''
            DataWriterUser                = 'OM19WRITE'
            DataWriterPassword            = ''
            EnableErrorReporting          = 'Never'
            SendCEIPReports               = '0'
            AcceptEndUserLicenseAgreement = '1'
            UseMicrosoftUpdate            = '0'
        }

        NativeConsole              = @{
            EnableErrorReporting          = 'Never'
            InstallLocation               = 'C:\Program Files\Microsoft System Center\Operations Manager'
            SendCEIPReports               = '0'
            UseMicrosoftUpdate            = '0'
            AcceptEndUserLicenseAgreement = '1'
        }

        WebConsole                 = @{
            ManagementServer              = ''
            WebSiteName                   = 'Default Web Site'
            WebConsoleAuthorizationMode   = 'Mixed'
            WebConsoleUseSSL              = '0'
            SendCEIPReports               = '0'
            UseMicrosoftUpdate            = '0'
            AcceptEndUserLicenseAgreement = '1'
        }

        ReportServer               = @{
            ManagementServer              = ''
            SRSInstance                   = ''
            DataReaderUser                = 'OM19READ'
            DataReaderPassword            = ''
            SendODRReports                = '0'
            UseMicrosoftUpdate            = '0'
            AcceptEndUserLicenseAgreement = '1'
        }
    }

    $arguments = $parameters[$Role.ToString()].GetEnumerator() | Sort-Object Key | ForEach-Object {
        $value = $_.Value
        if ([string]::IsNullOrWhiteSpace($value) -and $PSBoundParameters.ContainsKey($_.Key))
        {
            $value = $PSBoundParameters[$_.Key]
        }
        '/{0}:"{1}"' -f $_.Key, $value
    }
    
    switch ($Role)
    {
        { $Role -in 'FirstManagementServer', 'AdditionalManagementServer' }
        { 
            if ($Uninstall.IsPresent) { '/uninstall /silent /components:OMServer'; break }
            "/install /silent /components:OMServer $arguments" 
        }
        'NativeConsole'
        { 
            if ($Uninstall.IsPresent) { '/uninstall /silent /components:OMConsole'; break }
            "/install /silent /components:OMConsole $arguments" 
        }
        'WebConsole'
        { 
            if ($Uninstall.IsPresent) { '/uninstall /silent /components:OMWebConsole'; break }
            "/install /silent /components:OMWebConsole $arguments" 
        }
        'ReportServer'
        { 
            if ($Uninstall.IsPresent) { '/uninstall /silent /components:OMReporting'; break }
            "/install /silent /components:OMReporting $arguments" 
        }
    }
}

<#
.SYNOPSIS
    Locate and import OperationsManager module
.DESCRIPTION
    Locate and import OperationsManager module
.EXAMPLE
    Resolve-cScomModule
 
    Imports module or writes an error if not present
#>

function Resolve-cScomModule
{
    [CmdletBinding()]
    param ()
    
    if (Get-Module -Name OperationsManager) { return }

    $module = Get-Module -Name OperationsManager -ErrorAction SilentlyContinue -ListAvailable

    if (-not $module)
    {
        $module = Get-Module (Get-ChildItem -Path $env:ProgramFiles -Recurse -Filter OperationsManager.psd1 -ErrorAction SilentlyContinue | Select-Object -First 1) -ErrorAction SilentlyContinue
    }

    if (-not $module)
    {
        Write-Error -Exception ([System.IO.FileNotFoundException]::new('OperationsManager module not available.'))
        return
    }

    Import-Module -ModuleInfo $module -Force
}


<#
.SYNOPSIS
    Test if SCOM components are installed
.DESCRIPTION
    Test if SCOM components are installed
.EXAMPLE
    Test-cScomInstallationStatus -ScomComponent @{Role = 'NativeConsole'}
 
    returns boolean result indicating status.
.PARAMETER ScomComponent
    Hashtable resembling the DSC class ScomComponent.
#>

function Test-cScomInstallationStatus
{
    [OutputType([Bool])]
    [CmdletBinding()]
    param
    (
        [hashtable]
        $ScomComponent
    )

    if ($ScomComponent.Role -eq [ScomRole]::FirstManagementServer -or $ScomComponent.Role -eq [ScomRole]::FirstManagementServer)
    {
        if (Get-Command -Name Get-Package -ErrorAction SilentlyContinue)
        {
            return [bool](Get-Package -Name 'System Center Operations Manager Server' -ProviderName msi -ErrorAction SilentlyContinue)
        }

        return (Test-Path -Path (Join-Path -Path $ScomComponent.InstallLocation -ChildPath Server))
    }

    if ($ScomComponent.Role -eq [ScomRole]::NativeConsole)
    {
        if (Get-Command -Name Get-Package -ErrorAction SilentlyContinue)
        {
            return [bool](Get-Package -Name 'System Center Operations Manager Console' -ProviderName msi -ErrorAction SilentlyContinue)
        }

        return (Test-Path -Path (Join-Path -Path $ScomComponent.InstallLocation -ChildPath Console))
    }

    if ($ScomComponent.Role -eq [ScomRole]::WebConsole)
    {
        $website = Get-Website -Name $ScomComponent.WebSiteName -ErrorAction SilentlyContinue
        if (-not $website) { return $false }
        return $true
    }

    if ($ScomComponent.Role -eq [ScomRole]::ReportServer)
    {
        if (Get-Command -Name Get-Package -ErrorAction SilentlyContinue)
        {
            return [bool](Get-Package -Name 'System Center Operations Manager Reporting Server' -ProviderName msi -ErrorAction SilentlyContinue)
        }

        return (Test-Path -Path (Join-Path -Path $ScomComponent.InstallLocation -ChildPath Reporting))
    }
}