Public/Initialize-LMPOVSetup.ps1


<#
.SYNOPSIS
    This function initializes the Logic Monitor (POV) setup for SEs by automating various tasks required during POV setup.

.DESCRIPTION
    The Initialize-LMPOVSetup function sets up various components of the Logic Monitor POV.
    It can set up the website, portal metrics, alert analysis, and LM container.
    The setup for each component can be controlled individually or all at once.

.PARAMETER Website
    The name of the website to be set up. This parameter is used in both 'All' and 'Individual' parameter sets.

.PARAMETER WebsiteHttpType
    The HTTP type of the website. Defaults to "https". This parameter is used in both 'All' and 'Individual' parameter sets.

.PARAMETER PortalMetricsAPIUsername
    The username for the Portal Metrics API. Defaults to "lm_portal_metrics". This parameter is used in both 'All' and 'Individual' parameter sets.

.PARAMETER LogsAPIUsername
    The username for the Logs API. Defaults to "lm_logs". This parameter is used in both 'All' and 'Individual' parameter sets.

.PARAMETER SetupWebsite
    A switch to control the setup of the website. This parameter is used in the 'Individual' parameter set.

.PARAMETER SetupPortalMetrics
    A switch to control the setup of the portal metrics. This parameter is used in the 'Individual' parameter set.

.PARAMETER SetupAlertAnalysis
    A switch to control the setup of the alert analysis. This parameter is used in the 'Individual' parameter set.

.PARAMETER SetupLMContainer
    A switch to control the setup of the LM container. This parameter is used in the 'Individual' parameter set.

.PARAMETER LMContainerAPIUsername
    The username for the LM Container API. Defaults to "lm_container". This parameter is used in both 'All' and 'Individual' parameter sets.

.EXAMPLE
    Initialize-LMPOVSetup -RunAll -IncludeDefaults -Website example.com

    This command runs all setup processes including default options and creates a webcheck for example.com.

.INPUTS
    The function does not accept input from the pipeline.

.OUTPUTS
    The function does not return any output.

.NOTES
    The function throws an error if it fails to set up any component.
#>

Function Initialize-LMPOVSetup {

    [CmdletBinding(DefaultParameterSetName = 'Individual')]
    Param (
        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [String]$Website,

        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [String]$WebsiteHttpType = "https",

        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [string]$PortalMetricsAPIUsername = "lm_portal_metrics",

        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [string]$LogsAPIUsername = "lm_logs",

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupWebsite,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupPortalMetrics,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupAlertAnalysis,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupLMContainer,

        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [string]$LMContainerAPIUsername = "lm_container",

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$MoveMinimalMonitoring,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$CleanupDynamicGroups,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupWindowsLMLogs,

        [Parameter(ParameterSetName = 'Individual')]
        [Parameter(ParameterSetName = 'All')]
        [Switch]$IncludeDefaults,

        [Parameter(ParameterSetName = 'PostPOV-Readonly')]
        [Switch]$ReadOnlyMode,

        [Parameter(ParameterSetName = 'PostPOV-RevertReadonly')]
        [Switch]$RevertReadOnlyMode,

        [Parameter(ParameterSetName = 'Individual')]
        [Switch]$SetupCollectorServiceInsight,
        
        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'Individual')]
        [String]$WindowsLMLogsEventChannels = "Application,System",

        [Parameter(ParameterSetName = 'All')]
        [Switch]$RunAll
    )
    #Check if we are logged in and have valid api creds
    Begin {
        #Check for newer version of Logic.Monitor module
        Update-LogicMonitorSEModule -CheckOnly
    }
    Process {
        $PortalInfo = Get-LMAccountStatus
        If ($($PortalInfo)) {
            $PortalName = $PortalInfo.Portal
            $DeviceName = "$PortalName.logicmonitor.com"

            #Generate hastable of new dynamic groups to create
            $DynamicGroupList = @{
                "All Devices" = 'true()'
                "AWS Resources" = 'isAWSService()'
                "Azure Resources" = 'isAzureService()'
                "GCP Resources" = 'isGCPService()'
                "K8s Resources" = 'system.devicetype == "8"'
                "Dead Devices" = 'system.hoststatus == "dead" || system.hoststatus == "dead-collector" || system.gcp.status == "TERMINATED" || system.azure.status == "PowerState/stopped" || system.aws.stateName == "terminated"'
                "Palo Alto" = 'hasCategory("PaloAlto")'
                "Cisco ASA" = 'hasCategory("CiscoASA")'
                "Logs Enabled Devices" = 'hasPushModules("LogUsage")'
                "Netflow Enabled Devices" = 'isNetflow()'
                "Cisco UCS" = 'hasCategory("CiscoUCSFabricInterconnect") || hasCategory("CiscoUCSManager")'
                "Oracle" = 'hasCategory("OracleDB")'
                "Domain Controllers" = 'hasCategory("MicrosoftDomainController")'
                "Exchange Servers" = 'hasCategory("MSExchange")'
                "IIS" = 'hasCategory("MicrosoftIIS")'
                "Citrix XenApp" = 'hasCategory("CitrixBrokerActive") || hasCategory("CitrixMonitorServiceV2") || hasCategory("CitrixLicense") || hasCategory("CitrixEUEM")'

            }
            
            $GitubURI = "https://raw.githubusercontent.com/stevevillardi"

            #If readonly mode, siwtch all users to readonly and record previous role permissions for role back
            If($ReadOnlyMode){
                $Users = Get-LMUser | Where-Object {$_.apionly -eq $False -and $_.username -ne "lmsupport"}
                Foreach($User in $Users){
                    If($User.Note -notlike "Previous Roles:*"){
                        $PreviousRoles = $User.roles.Name -Join(",")
                        $UpdatedUser = Set-LMUser -Id $User.Id -RoleNames @("readonly") -Note "Previous Roles: $PreviousRoles"
                        If($UpdatedUser){
                            Write-Host "[INFO]: Previous role info ($($User.roles.Name -Join(","))) stored for user $($User.username), successfully converted to readonly role." -ForegroundColor Yellow
                        }
                    }
                    Else{
                        Write-Host "[INFO]: User $($User.username) previously converted, skipping processing." -ForegroundColor Gray
                    }
                }
            }
            #Revert any previous set readonly roles back to their original state
            If($RevertReadOnlyMode){
                $Users = Get-LMUser | Where-Object {$_.apionly -eq $False -and $_.username -ne "lmsupport"}
                Foreach($User in $Users){
                    $PreviousRoles = ($User.Note -Split "Previous Roles: ")[1] -Split (",")
                    If($PreviousRoles){
                        $UpdatedUser = Set-LMUser -Id $User.Id -RoleNames $PreviousRoles -Note " "
                        If($UpdatedUser){
                            Write-Host "[INFO]: Previous role info ($(($User.Note -Split "Previous Roles: ")[1])) found for user $($User.username), successfully reverted readonly role." -ForegroundColor Yellow
                        }
                    }
                    Else{
                        Write-Host "[WARN]: No previous role info found for user $($User.username), skipping role revert for user." -ForegroundColor Yellow
                    }
                }
            }

            #Create example collector service insight
            If($SetupCollectorServiceInsight -or $RunAll){
                $ServiceInsightProps = @{
                    device = @(
                        @{
                            deviceGroupFullPath = "Devices by Type/Collectors";
                            deviceDisplayName = "*";
                            deviceProperties = @()
                        }
                    )
                } | ConvertTo-Json -Depth 3

                #Create new SI resource
                $ServiceInsightResource = Get-LMDevice -name "LogicMonitorCollectorHealth"
                If(!$ServiceInsightResource){
                    $ServiceInsightResource = New-LMDevice -name "LogicMonitorCollectorHealth" -DisplayName "LogicMonitor: Collector Health" -PreferredCollectorId -4 -DeviceType 6 -Properties @{"predef.bizservice.members"=$ServiceInsightProps;"predef.bizService.evalMembersInterval"="30"}
                    If($ServiceInsightResource){
                        Write-Host "[INFO]: Successfully created service insight resource (LogicMonitor: Collector Health)"
                        #Upload SI datasource from xml
                        Try{
                            $SIDatasource = (Invoke-WebRequest -Uri "$GitubURI/LogicMonitor-Dashboards/main/POV/LogicMonitor_Collector_Health.xml" -UseBasicParsing).Content
                            Import-LMLogicModule -File $SIDatasource -Type datasource -ErrorAction Stop
                        }
                        Catch{
                            #Oops
                            Write-Host "[ERROR]: Unable to import SI template from source: $_" -ForegroundColor Red
                        }
                    }
                    Else{
                        #Oops
                        Write-Host "[ERROR]: Failed to create service insight resource (LogicMonitor: Collector Health), review error message and try again." -ForegroundColor Red
                    }
                }
                Else{
                    Write-Host "[INFO]: Service insight resource (LogicMonitor: Collector Health) already exists, skipping creation" -ForegroundColor Gray
                    #Upload SI datasource from xml
                    If(!$(Get-LMDatasource -DisplayName "LogicMonitor Collector Health")){
                        Try{
                            $SIDatasource = (Invoke-WebRequest -Uri "$GitubURI/LogicMonitor-Dashboards/main/POV/LogicMonitor_Collector_Health.xml").Content
                            Import-LMLogicModule -File $SIDatasource -Type datasource -ErrorAction Stop
                        }
                        Catch{
                            #Oops
                            Write-Host "[WARN]: Unable to import SI template from source: $_" -ForegroundColor Yellow
                        }
                    }
                    Else{
                        Write-Host "[INFO]: Service insight aggregate datasource (LogicMonitor Collector Health) already exists, skipping import" -ForegroundColor Gray
                    }
                }
            }

            #Create readonly API use for Portal Metrics
            If ($SetupPortalMetrics -or $RunAll) {
                $CheckAPIUser = Get-LMUser -Name "$PortalMetricsAPIUsername"
                $CheckPortalDevice = Get-LMDevice -Name $DeviceName

                If(!$CheckAPIUser -and !$CheckPortalDevice){
                    Write-Host "[INFO]: Setting up API user: $PortalMetricsAPIUsername"
                    $APIUser = New-LMAPIUser -Username "$PortalMetricsAPIUsername" -note "Auto provisioned for use with LM Portal Metrics Datasources" -RoleNames @("readonly")
                    If ($APIUser) {
                        Write-Host "[INFO]: Successfully setup API user: $PortalMetricsAPIUsername"
        
                        Write-Host "[INFO]: Creating readonly API token for user: $PortalMetricsAPIUsername"
                        $APIInfo = New-LMAPIToken -id $APIUser.id -Note "Auto provisioned for use with LM Portal Metrics Datasource"
                    }
        
                    #Setup portal mertics device if we have a valid API token
                    If ($APIInfo) {
                        Write-Host "[INFO]: Successfully created API token for user: $PortalMetricsAPIUsername | $($APIInfo.accessId) | $($APIInfo.accessKey)"
        
                        If ($PortalName) {
                            $PortalDeviceGroup = New-LMDeviceGroup -Name "LogicMonitor Portal Metrics" -AppliesTo "hasCategory(`"LogicMonitorPortal`")" -ParentGroupName "Devices by Type"
                            If ($PortalDeviceGroup) {
                                Write-Host "[INFO]: Created Portal Metrics dynamic group in Devices by Type: $($PortalDeviceGroup.name)"
                            }
    
                            $CollectorId = (Get-LMCollector | Where-Object {$_.collectorSize -ne "n/a"} | Select-Object -Last 1).id
                            Write-Host "[INFO]: Creating Portal Metrics resource: $DeviceName"
                            $PortalDevice = New-LMDevice -Name $DeviceName -DisplayName $DeviceName -Description "Auto provisioned resource to collect LM Portal Metrics" -Properties @{"lmaccess.id" = $APIInfo.accessId; "lmaccess.key" = $APIInfo.accessKey; "lmaccount" = $PortalName } -PreferredCollectorId  $CollectorId
                            If ($PortalDevice) {
                                Write-Host "[INFO]: Successfully created Portal Metrics resource: $DeviceName"
                            }
                        }
                    }
                }
                Else{
                    Write-Host "[INFO]: API User ($PortalMetricsAPIUsername) or portal metrics device ($DeviceName) already exists in portal, skipping setup for portal metrics" -ForegroundColor Gray
                }
            }

            #Setup Company Website
            If (($SetupWebsite -or $RunAll) -and $Website) {
                Write-Host "[INFO]: Setting up external webcheck for: $Website"
                $Website = $Website.split("//")[-1] #Make sure http/https is not in the entered site name
                $WebsiteResult = New-LMWebsite -Webcheck -Name $Website -HttpType $WebsiteHttpType -WebsiteDomain $Website
                If ($WebsiteResult) {
                    Write-Host "[INFO]: Successfully setup external webcheck for: $Website"
                }
            }

            #Move minimal monitoring folder into devices by type
            If ($MoveMinimalMonitoring -or $RunAll) {
                $DeviceFolderId = (Get-LMDeviceGroup -Name "Devices by Type").id
                $MinimalFolderId = (Get-LMDeviceGroup -Name "Minimal Monitoring").id
                If ($DeviceFolderId -and $MinimalFolderId) {
                    Write-Host "[INFO]: Moving minimal monitoring folder into Devices by Type"
                    $MinimalFolderGroup = Set-LMDeviceGroup -id $MinimalFolderId -ParentGroupId $DeviceFolderId
                    If ($MinimalFolderGroup) {
                        Write-Host "[INFO]: Successfully moved minimal monitoring folder into Devices by Type"
                    }
                    $MinimalFolderAppliesTo = (Get-LMDeviceGroup -Name "Minimal Monitoring").appliesTo
                    If ($MinimalFolderAppliesTo) {
                        Write-Host "[INFO]: Updating Minimal Monitoring folder to exclude Meraki and Portal Metrics resources"
                        $MinimalFolderAppliesTo = "system.sysinfo == `"`" && system.sysoid == `"`" && isDevice() && !(system.virtualization) && (monitoring != `"basic`") && system.devicetype != `"8`" && !hasCategory(`"LogicMonitorPortal`") && !hasCategory(`"MerakiAPIOrg`") && !hasCategory(`"MerakiAPINetwork`")"
                        $MinimalFolder = Set-LMDeviceGroup -Id $MinimalFolderId -AppliesTo $MinimalFolderAppliesTo
                        If ($MinimalFolder) {
                            Write-Host "[INFO]: Successfully updated minimal monitoring appliesTo query"
                        }
                    }
                }
            }

            #Cleanup dynamic groups will add Linux_SSH to the Linux folder and delete the Misc folder
            If ($CleanupDynamicGroups -or $RunAll) {
                Write-Host "[INFO]: Cleaning up default dynamic groups"
                $LinuxDeviceGroupId = (Get-LMDeviceGroup -Name "Linux Servers" | Where-Object {$_.fullPath -like "Devices by Type*"}).id
                $MiscDeviceGroupId = (Get-LMDeviceGroup -Name "Misc").id
                If(!$DeviceFolderId){
                    $DeviceFolderId = (Get-LMDeviceGroup -Name "Devices by Type").id
                }
                If ($LinuxDeviceGroupId) {
                    $ModifedLinuxGroup = Set-LMDeviceGroup -Id $LinuxDeviceGroupId -AppliesTo "isLinux() || hasCategory(`"Linux_SSH`")"
                    If ($ModifedLinuxGroup) {
                        Write-Host "[INFO]: Updated Linux Servers group to include Linux_SSH devices"
                    }
                }
                If ($MiscDeviceGroupId) {
                    $MiscDeviceGroup = Remove-LMDeviceGroup -Id $MiscDeviceGroupId
                    If ($ModifedLinuxGroup) {
                        Write-Host "[INFO]: Removed Misc devices group from Devices by Type"
                    }
                }
                Write-Host "[INFO]: Creating additional default dynamic groups"
                Foreach($Group in $DynamicGroupList.GetEnumerator()){
                    If(!$(Get-LMDeviceGroup -Name $Group.Name)){
                        $NewGroup = New-LMDeviceGroup -Name $Group.Name -ParentGroupId $DeviceFolderId -AppliesTo $Group.Value
                        If($NewGroup){
                            Write-Host "[INFO]: Created new dynamic group: $($Group.Name)"
                        }
                    }
                    Else{
                        Write-Host "[INFO]: Dynamic group: $($Group.Name) already exists, skipping creation" -ForegroundColor Gray
                    }
                }
            }

            #Add k8s role with proper permissions
            If($RunAll -or $SetupLMContainer){
                $LMContainerAPIRoleName = "lm-container"
                $LMContainerAPIUser = Get-LMUser -Name "$LMContainerAPIUsername"
                $LMContainerAPIRole = Get-LMRole -Name $LMContainerAPIRoleName

                If(!$LMContainerAPIRole){
                    Write-Host "[INFO]: Setting up LM Container API Role: $LMContainerAPIRoleName"
                    $LMContainerAPIRole = New-LMRole -Name $LMContainerAPIRoleName -ResourcePermission manage -LogsPermission manage -DashboardsPermission manage -SettingsPermission manage-collectors -Description "Auto provisioned to allow for API token creation for use with lm-container"
                    If($LMContainerAPIRole){
                        Write-Host "[INFO]: Successfully setup API role: $LMContainerAPIRoleName"
                    }
                }
                Else{
                    Write-Host "[INFO]: LM Container API Role ($LMContainerAPIRoleName) already exists in portal, skipping setup" -ForegroundColor Gray
                }

                If(!$LMContainerAPIUser){
                    Write-Host "[INFO]: Setting up LM Container API user: $LMContainerAPIUsername"
                    $LMContainerAPIUser = New-LMAPIUser -Username "$LMContainerAPIUsername" -note "Auto provisioned for use with LM Container" -RoleNames @($LMContainerAPIRoleName)
                    If ($LMContainerAPIUser) {
                        Write-Host "[INFO]: Successfully setup API user: $LMContainerAPIUsername"
                    }
                }
            }

            If($SetupWindowsLMLogs -or $RunAll){
                $LogsAPIRoleName = "lm-logs-ingest"
                $LogsAPIUser = Get-LMUser -Name "$LogsAPIUsername"
                $LogsAPIRole = Get-LMRole -Name $LogsAPIRoleName
                If(!$LogsAPIRole){
                    Write-Host "[INFO]: Setting up LM Logs API Role: $LogsAPIRoleName"
                    $LogsAPIRole = New-LMRole -Name $LogsAPIRoleName -ResourcePermission view -LogsPermission manage -Description "Auto provisioned to allow for windows events ingest via datasource"
                    If($LogsAPIRole){
                        Write-Host "[INFO]: Successfully setup API role: $LogsAPIRoleName"
                    }
                }
                Else{
                    Write-Host "[INFO]: LM Logs API Role ($LogsAPIRoleName) already exists in portal, skipping setup" -ForegroundColor Gray
                }

                If(!$LogsAPIUser){
                    Write-Host "[INFO]: Setting up LM Logs API user: $LogsAPIUsername"
                    $LogsAPIUser = New-LMAPIUser -Username "$LogsAPIUsername" -note "Auto provisioned for use with Windows LM Logs Datasource" -RoleNames @($LogsAPIRoleName)
                    If ($LogsAPIUser) {
                        Write-Host "[INFO]: Successfully setup API user: $LogsAPIUsername"
        
                        Write-Host "[INFO]: Creating administrator API token for user: $LogsAPIUsername"
                        $LMLogsAPIINfo = New-LMAPIToken -id $LogsAPIUser.id -Note "Auto provisioned for use with Windows LM Logs Datasource"
                    }
        
                    #Import Datasource and apply properties to windows server group
                    If ($LMLogsAPIINfo) {
                        Write-Host "[INFO]: Successfully created API token for user: $LogsAPIUsername | $($LMLogsAPIINfo.accessId) | $($LMLogsAPIINfo.accessKey)"
        
                        $WindowsServerDeviceGroup =  Get-LMDeviceGroup -Name "Windows Servers" | Where-Object {$_.fullPath -like "Devices by Type*"}


                        If ($WindowsServerDeviceGroup) {
                            Write-Host "[INFO]: Adding API properties to Windows Server device group"
                            $UpdatedWindowsServerDeviceGroup = Set-LMDeviceGroup -Id $WindowsServerDeviceGroup.Id -Properties @{"lmaccess.id" = $LMLogsAPIINfo.accessId; "lmaccess.key" = $LMLogsAPIINfo.accessKey; "lmaccount" = $PortalName; "lmlogs.winevent.channels" = $WindowsLMLogsEventChannels; "lmlogs.winevent.detailed_message" = "false" }
                            If ($UpdatedWindowsServerDeviceGroup) {
                                Write-Host "[INFO]: Successfully updated Windows Server device group for LM Logs"
                            }
                        }


                    }
                }
                Else{
                    Write-Host "[INFO]: LM Logs API User ($LogsAPIUsername) already exists in portal, skipping setup" -ForegroundColor Gray
                }

                #Import LM Logs Datasource
                $LogsDatasource = Get-LMDatasource -Name "Windows_Events_LMLogs"
                If(!$LogsDatasource){
                    Import-LMExchangeModule -LMExchangeId "2cb0a988-487a-48a6-b332-62003ef3b3dc" #core module
                    Start-Sleep -Seconds 5 #Added manual pause to ensure datasource is available after importing from the exchange
                    $LogsDatasource = Set-LMDatasource -Name Windows_Events_LMLogs -appliesTo "isWindows() && lmlogs.winevent.channels && lmaccess.id && lmaccess.key && lmaccount"
                    If($LogsDatasource){
                        Write-Host "[INFO]: Successfully added core module (Windows_Events_LMLogs) and updated appliesto logic"
                    }
                    Else{
                        Write-Host "[WARN]: Successfully added core module (Windows_Events_LMLogs) but was unable to modify the appliesTo criteria, please manually set the appliesTo criteria for the datasource to apply: 'isWindows() && lmlogs.winevent.channels && lmaccess.id && lmaccess.key && lmaccount'" -ForegroundColor Yellow
                    }
                }
                Else{
                    Write-Host "[INFO]: LM Logs core logicmodule ($($LogsDatasource.displayname)) already exists in portal, skipping setup" -ForegroundColor Gray
                }
            }

            If($IncludeDefaults -or $SetupAlertAnalysis){
                #Deploy AlertDuration dashboard
                Write-Host "[INFO]: Setting up Alert Analysis Dashboard and LogicModules from repo."
                $CheckPortalDevice = Get-LMDevice -Name $DeviceName
                If($CheckPortalDevice){
                    #Setup alert analysis package
                        #Import default DSes
                        $ModuleList = @(
                            @{
                                name = "LogicMonitor_Device_Alert_Statistics.xml"
                                type = "datasource"
                                repo = "LogicMonitor-Dashboards/main/Suites/Alert%20Duration"
                            },
                            @{
                                name = "LogicMonitor_DeviceGroup_Alert_Statistics.xml"
                                type = "datasource"
                                repo = "LogicMonitor-Dashboards/main/Suites/Alert%20Duration"
                            },
                            @{
                                name = "LogicMonitor_Portal_Alert_Statistics_Cache.xml"
                                type = "datasource"
                                repo = "LogicMonitor-Dashboards/main/Suites/Alert%20Duration"
                            },
                            @{
                                name = "LogicMonitor_Portal_Alert_Statistics.xml"
                                type = "datasource"
                                repo = "LogicMonitor-Dashboards/main/Suites/Alert%20Duration"
                            }
                        )

                        Foreach($Module in $ModuleList){
                            $ModuleName = $Module.name.Split(".")[0]
                            $LogicModule = Get-LMDataSource -name $ModuleName
                            If(!$LogicModule){
                                Try{
                                    $LogicModule = (Invoke-WebRequest -Uri "$GitubURI/$($Module.repo)/$($Module.name)").Content
                                    Import-LMLogicModule -File $LogicModule -Type $Module.type -ErrorAction Stop
                                    Write-Host "[INFO]: Successfully imported $ModuleName datasource"
                                }
                                Catch{
                                    #Oops
                                    Write-Host "[ERROR]: Unable to import $ModuleName LogicModule from source: $_" -ForegroundColor Red
                                }
                            }
                            Else{
                                Write-Host "[INFO]: LogicModule $ModuleName already exists, skipping import" -ForegroundColor Gray
                            }
                        }

                    $AlertAnalysisProps = @{
                        "alert.analysis.period"="7"
                        "alert.analysis.excludeUnACKedAlerts"="false"
                        "alert.analysis.excludeSDTedAlerts"="false"
                        "alert.analysis.datasourceList"="*"
                        "alert.analysis.minimumTimeInSeconds"="0"
                    }

                    Set-LMDevice -Id $CheckPortalDevice.Id -Properties $AlertAnalysisProps | Out-Null
                    $AlertDurationRootFolder = (Get-LMDashboardGroup -Name "LogicMonitor").Id
                    If(!$AlertDurationRootFolder){$AlertDurationRootFolder = 1}

                    Try{
                        $AlertDurationDashboardFile = (Invoke-WebRequest -Uri "$GitubURI/LogicMonitor-Dashboards/main/Suites/Alert%20Duration/Alert_Analysis_Overview.json").Content
                        $AlertDurationDashboard = Get-LMDashboard -Name "Alert Analysis Overview"
                        If(!$AlertDurationDashboard){
                            $ImportAlertDurationDashboard = Import-LMDashboard -File $AlertDurationDashboardFile -ParentGroupId $AlertDurationRootFolder
                            Write-Host "[INFO]: Successfully imported Alert Analysis Dashboard"
                        }
                        Else{
                            Write-Host "[INFO]: Alert Analysis Dashboard already exists, skipping import" -ForegroundColor Gray
                        }
                    }
                    Catch{
                        #Oops
                        Write-Host "[ERROR]: Unable to import Alert Analysis dashboard from source: $_" -ForegroundColor Red
                    }
                }
                Else{
                    Write-Host "[WARN]: Unable to import Alert Analysis dashboard template: PortalMetrics device not found, please deploy before attempting to deploy" -ForegroundColor Yellow
                }
            }
            
             #Setup common default options/imports
             If($IncludeDefaults){
                #Setup new user welcome message
                $MessageSubject = "LogicMonitor Account Created - (##USER##)"
                #Message Template
                $MessageTemplate = "PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQvc3RyaWN0LmR0ZCI+PEhUTUw+PEhFQUQ+PFRJVExFPjwvVElUTEU+PC9IRUFEPjxCT0RZPjx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSI1IiBjZWxsc3BhY2luZz0iNSIgc3R5bGU9ImhlaWdodDo0MDBweDsgd2lkdGg6NTUwcHgiPgoJPHRib2R5PgoJCTx0cj4KCQkJPHRkPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+QSBMb2dpY01vbml0b3IgYWNjb3VudCBoYXMgYmVlbiBjcmVhdGVkIGZvciB5b3UuIEhlcmUmIzM5O3Mgd2hhdCB5b3UgbmVlZCB0byBrbm93IHRvIGdldCBzdGFydGVkOjwvc3Bhbj4KCQkJPGRpdj48YnIgLz4KCQkJPHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5Mb2dnaW5nIGluOjwvc3Bhbj4KCQkJPHVsPgoJCQkJPGxpPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+WW91IGNhbiBsb2cgaW4gdG8geW91ciBhY2NvdW50IGF0Jm5ic3A7PGEgaHJlZj0iaHR0cDovLyMjQ09NUEFOWSMjIyNET01BSU4jIy8iIHRhcmdldD0iX2JsYW5rIj4jI0NPTVBBTlkjIyMjRE9NQUlOIyM8L2E+PC9zcGFuPjwvbGk+CgkJCQk8bGk+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5Zb3UgbXVzdCBsb2cgaW4gd2l0aCB0aGUgdXNlciAmcXVvdDs8c3Ryb25nPiMjVVNFUiMjPC9zdHJvbmc+JnF1b3Q7IGFuZCB0aGUgcGFzc3dvcmQgY3JlYXRlZCZuYnNwO2ZvciB5b3VyIHVzZXIgYWNjb3VudDwvc3Bhbj48L2xpPgoJCQk8L3VsPgoKCQkJPHA+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij48c3Ryb25nPk5vdGU6PC9zdHJvbmc+Jm5ic3A7SXQgaXMgcG9zc2libGUgdGhpcyBhY2NvdW50IHdhcyBnZW5lcmF0ZWQgYnkgeW91ciBMb2dpY01vbml0b3IgYWRtaW5pc3RhdG9yLCBpZiB5b3UgZG8gbm90IGtub3cgdGhlIHBhc3N3b3JkLCB5b3UgY2FuIHJlc2V0IHRoZSBwYXNzd29yZCBieSB2aXNpdGluZzogPGEgaHJlZj0iaHR0cDovLyMjQ09NUEFOWSMjIyNET01BSU4jIy8iIHRhcmdldD0iX2JsYW5rIj4jI0NPTVBBTlkjIyMjRE9NQUlOIyM8L2E+Jm5ic3A7YW5kIGNob29zaW5nICZxdW90OzxzdHJvbmc+SSBmb3Jnb3QgbXkgcGFzc3dvcmQ8L3N0cm9uZz4mcXVvdDs8L3NwYW4+PC9wPgoJCQk8L2Rpdj4KCgkJCTxkaXY+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5MZWFybmluZyBhYm91dCBMb2dpY01vbml0b3I6PC9zcGFuPgoKCQkJPHVsPgoJCQkJPGxpPgoJCQkJPHA+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxM3B4Ij5Mb2cgaW50byB5b3VyIHBvcnRhbCBhbmQgY2xpY2sgdGhlIFRyYWluaW5nIGxpbmsgaW4gdGhlIHVwcGVyIHJpZ2h0IGhhbmQgY29ybmVyLiBZb3Ugd2lsbCBmaW5kIGEgJnF1b3Q7R2V0dGluZyBTdGFydGVkIHdpdGggTG9naWNNb25pdG9yJnF1b3Q7IGNvdXJzZSB0aGVyZSB0byBoZWxwIHlvdSBiZWdpbiB5b3VyIExvZ2ljTW9uaXRvciBqb3VybmV5PC9zcGFuPjwvcD4KCQkJCTwvbGk+CgkJCQk8bGk+CgkJCQk8cD48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPlJlYWQgb3VyIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmxvZ2ljbW9uaXRvci5jb20vc3VwcG9ydC9nZXR0aW5nLXN0YXJ0ZWQvaS1qdXN0LXNpZ25lZC11cC1mb3ItbG9naWNtb25pdG9yLW5vdy13aGF0LzEtYWJvdXQtdGhlLWxvZ2ljbW9uaXRvci1zb2x1dGlvbi8iPkdldHRpbmcgU3RhcnRlZCBHdWlkZTwvYT48L3NwYW4+PC9wPgoJCQkJPC9saT4KCQkJCTxsaT4KCQkJCTxwPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+U2lnbiB1cCBmb3IgYSA8YSBocmVmPSJodHRwczovL3d3dy5sb2dpY21vbml0b3IuY29tL2xpdmUtdHJhaW5pbmctd2ViaW5hcnMvIj53ZWJpbmFyPC9hPiBvciByZXZpZXcgb3VyIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmxvZ2ljbW9uaXRvci5jb20vYWNhZGVteS8iPm90aGVyIGF2YWlsYWJsZSByZXNvdXJjZXM8L2E+Jm5ic3A7PC9zcGFuPiZuYnNwOzwvcD4KCQkJCTwvbGk+CgkJCQk8bGk+CgkJCQk8cD48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPkNoZWNrIG91dCBvdXIgPGEgaHJlZj0iaHR0cHM6Ly93d3cubG9naWNtb25pdG9yLmNvbS9zdXBwb3J0LyI+c3VwcG9ydCBjZW50ZXI8L2E+PC9zcGFuPjwvcD4KCQkJCTwvbGk+CgkJCQk8bGk+CgkJCQk8cD48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPlN1YnNjcmliZSB0byBvdXIgPGEgaHJlZj0iaHR0cHM6Ly93d3cubG9naWNtb25pdG9yLmNvbS9yZWxlYXNlLW5vdGVzLyI+cmVsZWFzZSBub3RlczwvYT48L3NwYW4+PC9wPgoJCQkJPC9saT4KCQkJPC91bD4KCQkJPC9kaXY+CgoJCQk8ZGl2PjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+SWYgeW91IG5lZWQgaGVscDo8L3NwYW4+CgoJCQk8dWw+CgkJCQk8bGk+CgkJCQk8cD48c3BhbiBzdHlsZT0iZm9udC1zaXplOjEzcHgiPkNvbnRhY3QgPGEgaHJlZj0iaHR0cHM6Ly9zdXBwb3J0LmxvZ2ljbW9uaXRvci5jb20vaGMvZW4tdXMvcmVxdWVzdHMvbmV3LyI+c3VwcG9ydDwvYT48L3NwYW4+PC9wPgoJCQkJPC9saT4KCQkJCTxsaT4KCQkJCTxwPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+U2lnbiB1cCBmb3IgYSA8YSBocmVmPSJodHRwczovL3d3dy5sb2dpY21vbml0b3IuY29tL3N1cHBvcnQvY2VydGlmaWNhdGlvbi9sb2dpY21vbml0b3ItY2VydGlmaWNhdGlvbi1wcm9ncmFtcy8iPmNlcnRpZmljYXRpb24gY291cnNlPC9hPiBvciBmaW5kIGEgY2VydGlmaWVkIGluZGl2aWR1YWwgd2l0aGluIHlvdXIgY29tcGFueTwvc3Bhbj48L3A+CgkJCQk8L2xpPgoJCQk8L3VsPgoJCQk8L2Rpdj4KCgkJCTxkaXY+PGJyIC8+CgkJCTxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+SGFwcHkgTW9uaXRvcmluZyE8L3NwYW4+PGJyIC8+CgkJCTxzcGFuIHN0eWxlPSJmb250LXNpemU6MTNweCI+VGhlIExvZ2ljTW9uaXRvciB0ZWFtPC9zcGFuPjwvZGl2PgoJCQk8L3RkPgoJCTwvdHI+Cgk8L3Rib2R5Pgo8L3RhYmxlPgo8L0JPRFk+PC9IVE1MPg=="
                $MessageTemplate = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($MessageTemplate))

                Try{
                    $MessageResult = Set-LMNewUserMessage -MessageBody $MessageTemplate -MessageSubject $MessageSubject -ErrorAction Stop
                    Write-Host "[INFO]: Successfully updated new user welcome email template"
                }
                Catch{
                    Write-Host "[ERROR]: Unable to modify new user welcome email template: $_" -ForegroundColor Red
                }

                #Set SSL_Cert DS to only alert on non self signed certs
                $SSLDatasourceName = "SSL_Certificates"
                $SSLDatasource = Get-LMDatasource -name $SSLDatasourceName
                If($SSLDatasource){
                    If($SSLDatasource.dataPoints.name.IndexOf("Alerting_DaysRemainingIfNotSelfSigned") -ne 1 -and $SSLDatasource.dataPoints.name.IndexOf("DaysRemaining") -ne 1){
                        Try{
                            $SSLDatasource.dataPoints[$SSLDatasource.dataPoints.name.IndexOf("Alerting_DaysRemainingIfNotSelfSigned")].alertExpr = "< 28 7 2"
                            $SSLDatasource.dataPoints[$SSLDatasource.dataPoints.name.IndexOf("DaysRemaining")].alertExpr = $null
                            $SSLResult = Set-LMDatasource -Id $SSLDatasource.id -Datapoints $SSLDatasource.datapoints -ErrorAction Stop
                            Write-Host "[INFO]: Successfully updated default alert thresholds on LogicModule ($SSLDatasourceName)"
                        }
                        Catch{
                            Write-Host "[ERROR]: Unable to modify default alert thresholds on LogicModule ($SSLDatasourceName): $_" -ForegroundColor Red
                        }
                    }
                    Else{
                        #Unable to find required datapoints for modification
                        Write-Host "[ERROR]: Unable to modify default alert thresholds on LogicModule ($SSLDatasourceName), expected datapoints not found" -ForegroundColor Red
                    }
                }
                Else{
                    #Unable to find SSL_Certificates DS
                    Write-Host "[ERROR]: Unable to locate LogicModule ($SSLDatasourceName), skipping alert threshold modification" -ForegroundColor Red
                }

                #Import default DSes
                $ModuleList = @(
                    @{
                        name = "Microsoft_Windows_Services_AD.xml"
                        type = "datasource"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    },
                    @{
                        name = "LogicMonitor_Collector_Configurations.xml"
                        type = "configsource"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    },
                    @{
                        name = "NoData_Metrics.xml"
                        type = "datasource"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    },
                    @{
                        name = "NoData_Tasks_By_Type_v2.xml"
                        type = "datasource"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    },
                    @{
                        name = "NoData_Tasks_Overall_v2.xml"
                        type = "datasource"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    },
                    @{
                        name = "NoData_Tasks_Discovery_v2.json"
                        type = "propertyrules"
                        repo = "LogicMonitor-Dashboards/main/POV"
                    }
                )
                Foreach($Module in $ModuleList){
                    $ModuleName = $Module.name.Split(".")[0]
                    $LogicModule = Switch($Module.type){
                        "datasource" {Get-LMDataSource -name $ModuleName}
                        "configsource" {Get-LMConfigSource -name $ModuleName}
                        "propertyrules" {Get-LMPropertySource -name $ModuleName}
                        default {Get-LMDataSource -name $ModuleName}
                    }
                    If(!$LogicModule){
                        Try{
                            $LogicModule = (Invoke-WebRequest -Uri "$GitubURI/$($Module.repo)/$($Module.name)").Content
                            Import-LMLogicModule -File $LogicModule -Type $Module.type -ErrorAction Stop
                            Write-Host "[INFO]: Successfully imported $ModuleName datasource"
                        }
                        Catch{
                            #Oops
                            Write-Host "[ERROR]: Unable to import $ModuleName LogicModule from source: $_" -ForegroundColor Red
                        }
                    }
                    Else{
                        Write-Host "[INFO]: LogicModule $ModuleName already exists, skipping import" -ForegroundColor Gray
                    }
                }

                #Deploy Dynamic Dashboards
                $DashboardList = @(
                    @{
                        name = "Dynamic_Dashboard_-_Network_Performance.json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    },
                    @{
                        name = "Dynamic_Dashboard_-_Windows.json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    },
                    @{
                        name = "Dynamic_Dashboard_-_Windows_(Textbox_Selector).json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    },
                    # @{
                    # name = "Dynamic_Dashboard_-_vCenter_VMs.json"
                    # repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    # },
                    @{
                        name = "Dynamic_Server-at-a-Glance__Linux(SNMP).json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    },
                    @{
                        name = "Dynamic_Server-at-a-Glance__Linux(SSH).json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    },
                    @{
                        name = "Dynamic_Server-at-a-Glance__Windows.json"
                        repo = "LogicMonitor-Dashboards/main/Dynamic%20Dashboards"
                    }
                )

                #Generate API Key and Role
                $DashboardAPIRoleName = "lm-dynamic-dashboards"
                $DashboardAPIUserName = "lm_dynamic_dashboards"
                $DashboardAPIRole = Get-LMRole -Name $DashboardAPIRoleName
                $DashboardAPIUser = Get-LMUser -Name $DashboardAPIUserName
                If(!$DashboardAPIRole){
                    $DashboardAPIRole = New-LMRole -Name $DashboardAPIRoleName -ResourcePermission view -DashboardsPermission manage -Description "Auto provisioned for use with dynamic dashboards"
                    Write-Host "[INFO]: Successfully generated required API role ($DashboardAPIRoleName) for dynamic dashboards"
                }
                If(!$DashboardAPIUser){
                    $DashboardAPIUser = New-LMAPIUser -Username "$DashboardAPIUserName" -note "Auto provisioned for use with dynamic dashboards" -RoleNames @($DashboardAPIRoleName)
                    Write-Host "[INFO]: Successfully generated required API user ($DashboardAPIUserName) for dynamic dashboards"
                }
                If($DashboardAPIRole -and $DashboardAPIUser){
                    $APIToken = New-LMAPIToken -Username $DashboardAPIUserName -Note "Auto provisioned for use with dynamic dashboards"
                    If($APIToken){
                        Write-Host "[INFO]: Successfully generated required API token for dynamic dashboards for user: $DashboardAPIUserName"
                    }
                }
                Else{
                    Write-Host "Unable to generate required API token for dynamic dashboards, manually update the required tokens to use dynamic dashboards"
                }

                Write-Host "[INFO]: Importing dynamic dashboards from repo."
                $DynamicDashboardGroup = (Get-LMDashboardGroup -Name "Dynamic Dashboards").Id
                If(!$DynamicDashboardGroup){
                    $DynamicDashboardGroup = (New-LMDashboardGroup -Name "Dynamic Dashboards" -ParentGroupId 1).Id
                }

                Foreach($Dashboard in $DashboardList){
                    Try{
                        $DashboardFile = (Invoke-WebRequest -Uri "$GitubURI/$($Dashboard.repo)/$($Dashboard.name)").Content
                        If(!$(Get-LMDashboard -Name $(($DashboardFile | ConvertFrom-Json).Name))){
                            $ImportedDashboard = Import-LMDashboard -File $DashboardFile -ReplaceAPITokensOnImport -APIToken $APIToken -ParentGroupId $DynamicDashboardGroup -ErrorAction Stop
                        }
                        Else{
                            Write-Host "[INFO]: Dashboard $($Dashboard.name) already exists, skipping import" -ForegroundColor Gray
                        }
                    }
                    Catch{
                        #Oops
                        Write-Host "[ERROR]: Unable to import $($Dashboard.name) template from source: $_" -ForegroundColor Red
                    }
                }

            }
        }
        Else {
            Write-Error "Please ensure you are logged in before running any commands, use Connect-LMAccount to login and try again."
        }
    }
    End {}
}