Public/Import-LMDashboard.ps1

<#
.SYNOPSIS
Imports LogicMonitor dashboards from various sources.

.DESCRIPTION
The `Import-LMDashboard` function allows you to import LogicMonitor dashboards from different sources, such as local files, GitHub repositories, or LogicMonitor dashboard groups. It supports importing dashboards in JSON format.

.PARAMETER FilePath
Specifies the path to a local file or directory containing the JSON dashboard files to import. If a directory is specified, all JSON files within the directory (and its subdirectories) will be imported.

.PARAMETER File
Specifies a single JSON dashboard file to import.

.PARAMETER GithubUserRepo
Specifies the GitHub repository (in the format "username/repo") from which to import JSON dashboard files.

.PARAMETER GithubAccessToken
Specifies the GitHub access token to use for authenticated requests. This is required for large repositories, as the GitHub API has rate limits for unauthenticated requests.

.PARAMETER ParentGroupId
Specifies the ID of the parent dashboard group under which the imported dashboards will be placed. This parameter is mandatory when importing from a file or GitHub repository.

.PARAMETER ParentGroupName
Specifies the name of the parent dashboard group under which the imported dashboards will be placed. This parameter is mandatory when importing from a file or GitHub repository.

.PARAMETER ReplaceAPITokensOnImport
Indicates whether to replace API tokens in the imported dashboards with a dynamically generated API token. This is useful for managing API access to the dashboards.

.PARAMETER APIToken
Specifies the API token to use for replacing API tokens in the imported dashboards. This parameter is required when `ReplaceAPITokensOnImport` is set to `$true`.

.PARAMETER PrivateUserName
Specifies the username of dashboard owner when creating dashboard as private.

.EXAMPLE
Import-LMDashboard -FilePath "C:\Dashboards" -ParentGroupId 12345 -ReplaceAPITokensOnImport -APIToken $apiToken
Imports all JSON dashboard files from the "C:\Dashboards" directory and its subdirectories. The imported dashboards will be placed under the dashboard group with ID 12345. API tokens in the imported dashboards will be replaced with the specified API token.

.EXAMPLE
Import-LMDashboard -GithubUserRepo "username/repo" -ParentGroupName "MyDashboards" -ReplaceAPITokensOnImport -APIToken $apiToken
Imports JSON dashboard files from the specified GitHub repository. The imported dashboards will be placed under the dashboard group with the name "MyDashboards". API tokens in the imported dashboards will be replaced with the specified API token.
#>

Function Import-LMDashboard {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, ParameterSetName = 'FilePath-GroupId')]
        [Parameter(Mandatory, ParameterSetName = 'FilePath-GroupName')]
        [String]$FilePath,

        [Parameter(Mandatory, ParameterSetName = 'File-GroupId')]
        [Parameter(Mandatory, ParameterSetName = 'File-GroupName')]
        [String]$File,

        [Parameter(Mandatory, ParameterSetName = 'Repo-GroupId')]
        [Parameter(Mandatory, ParameterSetName = 'Repo-GroupName')]
        [String]$GithubUserRepo, # format "username/repo"

        [Parameter(ParameterSetName = 'Repo-GroupId')]
        [Parameter(ParameterSetName = 'Repo-GroupName')]
        [String]$GithubAccessToken, #Required for large repos, github api is limited to 60 requests per hour when unauthenticated

        [Parameter(Mandatory, ParameterSetName = 'FilePath-GroupId')]
        [Parameter(Mandatory, ParameterSetName = 'File-GroupId')]
        [Parameter(Mandatory, ParameterSetName = 'Repo-GroupId')]
        [String]$ParentGroupId,

        [Parameter(Mandatory, ParameterSetName = 'FilePath-GroupName')]
        [Parameter(Mandatory, ParameterSetName = 'File-GroupName')]
        [Parameter(Mandatory, ParameterSetName = 'Repo-GroupName')]
        [String]$ParentGroupName,

        [Switch]$ReplaceAPITokensOnImport,

        $APIToken,

        [String]$PrivateUserName = ""
    )

    #Check if we are logged in and have valid api creds
    Begin {}
    Process {
        If ($Script:LMAuth.Valid) {
            $Results = @()
            $DashboardList = @()

            If ($ParentGroupName) {
                $ParentGroupId = (Get-LMDashboardGroup -Name $ParentGroupName | Select-Object -First 1 ).Id
            }
            If ($ParentGroupId) {
                $ParentGroupName = (Get-LMDashboardGroup -Id $ParentGroupId | Select-Object -First 1 ).Name
            }

            If ($FilePath) {
                If ((Get-Item $FilePath) -is [System.IO.DirectoryInfo]) {
                    $FullPath = (Resolve-Path $FilePath).Path
                    $Files = Get-ChildItem $FullPath -Recurse | Where-Object { ([IO.Path]::GetExtension($_.Name) -eq '.json') }
                    Foreach ($F in $Files) {
                        #Convert from json into object
                        $RawFile = Get-Content $F.FullName -Raw | ConvertFrom-Json
                        $DashboardList += @{
                            file       = $RawFile
                            path       = $($F.DirectoryName -split $FullPath)[1]
                            parentid   = $ParentGroupId
                            parentname = $ParentGroupName
                        }
                    }
                }
                Else {
                    If (!(Test-Path -Path $FilePath) -and (!([IO.Path]::GetExtension($FilePath) -eq '.json'))) {
                        Write-Error "File not found or is not a valid json file, check file path and try again"
                        Return
                    }

                    #Convert from json into object
                    $RawFile = Get-Content $FilePath -Raw | ConvertFrom-Json
                    $DashboardList += @{
                        file       = $RawFile
                        path       = ""
                        parentid   = $ParentGroupId
                        parentname = $ParentGroupName
                    }
                }
            }

            If ($File) {
                $DashboardList += @{
                    file       = $File | ConvertFrom-Json
                    path       = ""
                    parentid   = $ParentGroupId
                    parentname = $ParentGroupName
                }
            }

            If ($GithubUserRepo) {
                $Headers = @{}
                If ($GithubAccessToken) {
                    $Headers = @{"Authorization" = "token $GithubAccessToken" }
                }
                $Uri = "https://api.github.com/repos/$GithubUserRepo/git/trees/master?recursive=1"
                $RepoData = (Invoke-RestMethod -Uri $Uri -Headers $Headers[0] -WebSession $Headers[1]).tree | Where-Object { $_.Path -like "*.json" -and $_.Path -notlike "Packages/LogicMonitor_Dashboards*" } | Select-Object path, url
                If ($RepoData) {
                    $TotalItems = ($RepoData | Measure-Object).Count
                    Write-Information "[INFO]: Found $TotalItems JSON files from Github repo ($GithubUserRepo)"
                    Foreach ($Item in $RepoData) {
                        $EncodedDash = (Invoke-RestMethod -Uri $Item.url -Headers $Headers[0] -WebSession $Headers[1]).content
                        $DashboardList += @{
                            file       = [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($EncodedDash)) | ConvertFrom-Json
                            path       = [System.IO.Path]::GetDirectoryName($Item.path)
                            parentid   = $ParentGroupId
                            parentname = $ParentGroupName
                        }
                        
                        Write-Information "[INFO]: Successfully downloaded dashboard ($($Item.path)) from Github repo ($GithubUserRepo)"
                    }
                }
            }
    
            If ($ReplaceAPITokensOnImport -and !($APIToken)) {
                $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-Information "[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-Information "[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-Information "[INFO]: Successfully generated required API token for dynamic dashboards for user: $DashboardAPIUserName"
                    }
                }
                Else {
                    Write-Warning "[WARN]: Unable to generate required API token for dynamic dashboards, manually update the required tokens to use dynamic dashboards" 
                }
            }

            Foreach ($Dashboard in $DashboardList) {
                #Swap apiKeys for dynamic dashboards
                If ($ReplaceAPITokensOnImport) {
                    If ($APIToken) {
                        If ($Dashboard.file.widgetTokens.name -contains "apiKey") {
                            $KeyIndex = $Dashboard.file.widgetTokens.name.toLower().IndexOf("apikey")
                            $Dashboard.file.widgetTokens[$KeyIndex].value = $APIToken.accessKey
                        }
                        If ($Dashboard.file.widgetTokens.name -contains "apiID") {
                            $IdIndex = $Dashboard.file.widgetTokens.name.toLower().IndexOf("apiid")
                            $Dashboard.file.widgetTokens[$IdIndex].value = $APIToken.accessId
                        }
                    }
                }

                #Check if a path has been provided and check if folder exists in selected root folder, if not create
                If ($Dashboard.path) {
                    [Array]$SubFolders = $Dashboard.path -split "\\|/" | Where-Object { $_ }

                    For ($Index = 0; $Index -lt $($SubFolders | Measure-Object).Count; $Index++) {

                        If ($Index -eq 0) {
                            $DashboardGroup = Get-LMDashboardGroup -ParentGroupId $ParentGroupId | Where-Object { $_.Name -eq $SubFolders[$Index] }

                            If (!$DashboardGroup) {
                                Write-Information "[INFO]: Existing dashboard group not found for $($Subfolders[$Index]) creating new resource group under root group ($ParentGroupName)"
                                $NewDashboardGroup = New-LMDashboardGroup -Name $SubFolders[$Index] -ParentGroupId $ParentGroupId
                                $Dashboard.parentid = $NewDashboardGroup.id
                                $Dashboard.parentname = $NewDashboardGroup.name

                            }
                            Else {
                                $Dashboard.parentid = $DashboardGroup.id
                                $Dashboard.parentname = $DashboardGroup.name
                            }
                        }
                        Else {
                            $DashboardGroup = Get-LMDashboardGroup -Name $Subfolders[$Index] | Where-Object { $_.fullPath -like "$($Subfolders[0])*$($Subfolders[$Index])" }
                            
                            If (!$DashboardGroup) {

                                $NewDashboardParentGroup = Get-LMDashboardGroup -Name $Subfolders[$Index - 1] | Where-Object { $_.fullPath -like "$ParentGroupName*" -or $_.fullPath -eq $Subfolders[$Index - 1] }
                                Write-Information "[INFO]: Existing dashboard group not found for $($Subfolders[$Index]) creating new resource group under group ($($NewDashboardParentGroup.Name))"
                                $NewDashboardGroup = New-LMDashboardGroup -Name $SubFolders[$Index] -ParentGroupId $NewDashboardParentGroup.id

                                $Dashboard.parentid = $NewDashboardGroup.id
                                $Dashboard.parentname = $NewDashboardGroup.name

                            }
                            Else {
                                $Dashboard.parentid = $DashboardGroup.id
                                $Dashboard.parentname = $DashboardGroup.name
                            }
                        }
                    }
                }
    
                #Construct our object for import
                $Data = @{
                    description          = $Dashboard.file.description
                    groupId              = [int]$Dashboard.parentid
                    groupName            = $Dashboard.parentname
                    name                 = $Dashboard.file.name
                    sharable             = If ($PrivateUserName) { $False } Else { $True }
                    owner                = $PrivateUserName
                    template             = $Dashboard.file | Select-Object -ExcludeProperty group
                    widgetTokens         = $Dashboard.file.widgetTokens
                    widgetsConfigVersion = $Dashboard.file.widgetsConfigVersion
                }
    
                #Build header and uri
                $ResourcePath = "/dashboard/dashboards"
                
                Try {
                    $Data = ($Data | ConvertTo-Json -Depth 10)

                    $Headers = New-LMHeader -Auth $Script:LMAuth -Method "POST" -ResourcePath $ResourcePath -Data $Data
                    $Uri = "https://$($Script:LMAuth.Portal).logicmonitor.com/santaba/rest" + $ResourcePath
        
                    Resolve-LMDebugInfo -Url $Uri -Headers $Headers[0] -Command $MyInvocation -Payload $Data

                    #Issue request
                    $Response = Invoke-RestMethod -Uri $Uri -Method "POST" -Headers $Headers[0] -WebSession $Headers[1] -Body $Data
                    Write-Output "Successfully imported dashboard: $($Dashboard.file.name)"
    
                    $Results += (Add-ObjectTypeInfo -InputObject $Response -TypeName "LogicMonitor.Dashboard" )
    
                }
                Catch [Exception] {
                    Write-Output "Failed to import dashboard: $($Dashboard.file.name)"
                    $Proceed = Resolve-LMException -LMException $PSItem
                    If (!$Proceed) {
                        # Return
                    }
                }
            }
        }
        Else {
            Write-Error "Please ensure you are logged in before running any commands, use Connect-LMAccount to login and try again."
        }
    }
    End {
        $Results
    }
}