ArcGIS.Microsoft365.psm1

# Register-PnPManagementShellAccess
# Connect-PnPOnline -Url https://m4o365.sharepoint.com
# https://pnp.github.io/powershell/cmdlets/Add-PnPAlert.html
using namespace System.Net

Function New-ArcGISTermSet {

    [CmdletBinding(
        SupportsShouldProcess = $True,
        HelpURI = "https://doc.arcgis.com/en/sharepoint/latest/use-maps/understand-geotags-arcgis-maps.htm"
    )]
    Param()

    If ($PSCmdlet.ShouldProcess("Create the 'Esri' Term Group and the 'ArcGIS' Term Set in SharePoint Term Store", $null, $null)) {

        $EsriTermGroupName = "Esri"

        $EsriTermSetName = "ArcGIS"

        $OldEsriTermSetName = "M4SP"

        $TermGroups = Get-PnPTermGroup

        $HasEsriTermGroup = $false

        $HasEsriTermSet = $false

        Foreach ($TermGroup in $TermGroups) {
            if ( $TermGroup.Name -eq $EsriTermGroupName ) {
                $HasEsriTermGroup = $true
            }
        }

        # Create "Esri" term group if it is missing
        if (-not $HasEsriTermGroup) {
            New-PnPTermGroup -GroupName $EsriTermGroupName
            Start-Sleep -s 3
        }

        $TermSets = Get-PnPTermSet -TermGroup $EsriTermGroupName

        Foreach ($TermSet in $TermSets) {
            if ($TermSet.Name -eq $EsriTermSetName) {
                $HasEsriTermSet = $true
            }

            # Rename existing "M4SP" term set(if any) to "ArcGIS"
            if ($TermSet.Name -eq $OldEsriTermSetName) {
                $HasEsriTermSet = $true
                Set-PnPTermSet -Identity $OldEsriTermSetName -TermGroup $EsriTermGroupName -Name $EsriTermSetName
            }
        }

        # Create "ArcGIS" term set if it is missing
        if (-not $HasEsriTermSet) {
            New-PnPTermSet -Name $EsriTermSetName -TermGroup $EsriTermGroupName
        }
    }
}

$ArcGISOpenExtensionId = "com.esri.a4m365.arcgisSettings"

Function New-ArcGISConnection {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = "Specify ArcGIS Online organization (or ArcGIS Enterprise) URL")]
        [ValidateScript({ Test-ArcGISConnectionURL $_ })]
        [String] $ArcGISConnectionUrl,
        [Parameter(Mandatory = $true, HelpMessage = "Specify a title for this ArcGIS connection")]
        [String] $ArcGISConnectionTitle,
        [Parameter(Mandatory = $false)]
        [Switch] $Default
    )

    If ($PSCmdlet.ShouldProcess("Create a new ArcGIS connection configuration with the URL '${ArcGISConnectionUrl}' and Title 'ArcGISConnectionTitle'", $null, $null)) {

        # (Connect-MgGraph -Scopes "Organization.ReadWrite.All") 1> $null
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $Extension = Get-ArcGISExtension

            $ConfigurationProperty = @{
                "url"       = $ArcGISConnectionUrl;
                "title"     = $ArcGISConnectionTitle;
                "isDefault" = $Default.IsPresent;
            }

            if ($null -ne $Extension) {

                [Array]$ExtentionConfigurationArray = $Extension["connections"]

                if ($null -ne ($ExtentionConfigurationArray | Where-Object { $_.url.ToLower() -eq $ArcGISConnectionUrl.ToLower() })) {
                    Write-Warning "The ArcGIS connection with the URL '$ArcGISConnectionUrl' is already existing."
                    return
                }

                if ($Default.IsPresent) {
                    foreach ($ExtentionConfigurationItem in $ExtentionConfigurationArray) {
                        $ExtentionConfigurationItem.isDefault = $false
                    }
                }

                $ExtentionConfigurationArray += $ConfigurationProperty

                $Extension["connections"] = ConvertTo-Json $ExtentionConfigurationArray -Compress
                Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
            }
            else {
                # create the extension and add the first arcgis connection
                $ExtensionProperties = @{
                    "@odata.type"   = "#microsoft.graph.openTypeExtension";
                    "extensionName" = $ArcGISOpenExtensionId;
                    "connections"   = ConvertTo-Json @($ConfigurationProperty) -Compress
                }
                New-MgOrganizationExtension -OrganizationId $OrganizationId -AdditionalProperties $ExtensionProperties 1> $null
            }
        }
    }
}

Function Update-ArcGISConnection {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = "Specify ArcGIS Online organization (or ArcGIS Enterprise) URL")]
        [ValidateScript({ Test-ArcGISConnectionURL $_ })]
        [String] $ArcGISConnectionUrl,
        [Parameter(Mandatory = $true, HelpMessage = "Specify a title for this ArcGIS connection")]
        [String] $ArcGISConnectionTitle,
        [Parameter(Mandatory = $false)]
        [Switch] $Default
    )

    If ($PSCmdlet.ShouldProcess("Update the existing ArcGIS connection configuration with the URL '${ArcGISConnectionUrl}' and Title 'ArcGISConnectionTitle'", $null, $null)) {

        # (Connect-MgGraph -Scopes "Organization.ReadWrite.All") 1> $null
        if (Get-IsAuthenticated) {
            $Extension = Get-ArcGISExtension

            if ($null -ne $Extension) {
                [Array]$ExtentionConfigurationArray = $Extension["connections"]

                if ($ExtentionConfigurationArray.length -gt 0) {
                    [Array]$ExtentionConfigurationArray = $Extension["connections"]

                    [Array]$ExtentionConfigurationNewArray = $ExtentionConfigurationArray | Where-Object { $_.url.ToLower() -ne $ArcGISConnectionUrl.ToLower() }
                    if ($ExtentionConfigurationNewArray.length -eq $ExtentionConfigurationArray.length) {
                        Write-Warning "The ArcGIS connection with the URL '$ArcGISConnectionUrl' is not found."
                    }
                    else {
                        if ($Default.IsPresent) {
                            foreach ($ExtentionConfigurationItem in $ExtentionConfigurationNewArray) {
                                $ExtentionConfigurationItem.isDefault = $false
                            }
                        }
                        $ConfigurationProperty = @{
                            "url"       = $ArcGISConnectionUrl;
                            "title"     = $ArcGISConnectionTitle;
                            "isDefault" = $Default.IsPresent;
                        }
                        $ExtentionConfigurationNewArray += $ConfigurationProperty

                        $Extension["connections"] = ConvertTo-Json $ExtentionConfigurationNewArray -Compress

                        $OrganizationId = (Get-MgOrganization).Id
                        Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
                    }
                }
            } 
            else {
                Write-Warning "The ArcGIS connection with the URL '$ArcGISConnectionUrl' is not found."
            }
        }
    }
}

Function Set-CustomURLConnectionPermission {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName="allow")]
        [Switch] $Allow,
        [Parameter(Mandatory = $true, ParameterSetName="disallow")]
        [Switch] $Disallow
    )

    If ($PSCmdlet.ShouldProcess("Allow/Disallow manual URL connection", $null, $null)) {
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $DisallowManualUrl = $Disallow.IsPresent -or !$Allow.IsPresent

            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {
                [Array]$ExtentionConfigurationArray = $Extension["connections"]
                $Extension["connections"] = ConvertTo-Json $ExtentionConfigurationArray -Compress
                $Extension["disallowManualArcGISConnection"] = $DisallowManualUrl
                Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
            }
            else {
                # create the extension
                $ExtensionProperties = @{
                    "@odata.type"   = "#microsoft.graph.openTypeExtension";
                    "extensionName" = $ArcGISOpenExtensionId;
                    "disallowManualArcGISConnection" = $DisallowManualUrl
                }
                New-MgOrganizationExtension -OrganizationId $OrganizationId -AdditionalProperties $ExtensionProperties 1> $null
            }

            $Message = "allowed"
            if ($Disallow.IsPresent) {
                $Message = "disallowed"
            }
            Write-Host "Manual URL connection has been $Message." -ForegroundColor "Green"
        }
    }
}

Function Set-ArcGISOnlineConnectionPermission {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param(
        [Parameter(Mandatory = $true, ParameterSetName="allow")]
        [Switch] $Allow,
        [Parameter(Mandatory = $true, ParameterSetName="disallow")]
        [Switch] $Disallow
    )

    If ($PSCmdlet.ShouldProcess("Allow/Disallow ArcGIS Online connection", $null, $null)) {
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $DisallowAGOL = $Disallow.IsPresent -or !$Allow.IsPresent

            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {                
                [Array]$ExtentionConfigurationArray = $Extension["connections"]
                $Extension["connections"] = ConvertTo-Json $ExtentionConfigurationArray -Compress
                $Extension["disallowArcGISOnlineConnection"] = $DisallowAGOL
                Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
            }
            else {
                # create the extension and add the first arcgis connection
                $ExtensionProperties = @{
                    "@odata.type"   = "#microsoft.graph.openTypeExtension";
                    "extensionName" = $ArcGISOpenExtensionId;
                    "disallowArcGISOnlineConnection" = $DisallowAGOL
                }
                New-MgOrganizationExtension -OrganizationId $OrganizationId -AdditionalProperties $ExtensionProperties 1> $null
            }
            $Message = "allowed"
            if ($Disallow.IsPresent) {
                $Message = "disallowed"
            }
            Write-Host "ArcGIS Online connection has been $Message." -ForegroundColor "Green"
        }
    }
}

Function Remove-ArcGISConnection {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param
    (
        [Parameter(Mandatory = $true, HelpMessage = "Specify title of the ArcGIS connection configuration that you want to remove")]
        [String] $ArcGISConnectionTitle
    )

    If ($PSCmdlet.ShouldProcess("Remove ArcGIS connection configuration with the title '${ArcGISConnectionTitle}'", $null, $null)) {
        # (Connect-MgGraph -Scopes "Organization.ReadWrite.All") 1> $null
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $Extension = Get-ArcGISExtension

            if ($null -ne $Extension) {
                [Array]$ExtentionConfigurationArray = $Extension["connections"]

                if ($ExtentionConfigurationArray.length -gt 0) {

                    [Array]$ExtentionConfigurationNewArray = $ExtentionConfigurationArray | Where-Object { $_.title.ToLower() -ne $ArcGISConnectionTitle.ToLower() }

                    if ($ExtentionConfigurationNewArray.length -eq $ExtentionConfigurationArray.length) {
                        Write-Warning "The ArcGIS connection configuration with title '$ArcGISConnectionTitle' is not found."
                    }
                    else {
                        $Extension["connections"] = ConvertTo-Json $ExtentionConfigurationNewArray -Compress
                        Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
                        Write-Host "The ArcGIS connections configuration with the title '$ArcGISConnectionTitle' has been removed." -ForegroundColor "Green"
                    }
                }
            }            
        }
    }
}

Function Clear-AllTenantArcGISConnections {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param()

    If ($PSCmdlet.ShouldProcess("Remove all ArcGIS connection configurations", $null, $null)) {
        # (Connect-MgGraph -Scopes "Organization.ReadWrite.All") 1> $null
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {                
                $Extension["connections"] = ConvertTo-Json @() -Compress
                Update-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId -AdditionalProperties $Extension
            }

            Write-Host "All tenant-wide ArcGIS connection configurations have been removed." -ForegroundColor "Green"
        }
    }
}

Function Reset-TenantArcGISSettings {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param()

    If ($PSCmdlet.ShouldProcess("Remove all ArcGIS settings", $null, $null)) {
        # (Connect-MgGraph -Scopes "Organization.ReadWrite.All") 1> $null
        if (Get-IsAuthenticated) {
            $OrganizationId = (Get-MgOrganization).Id

            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {                
                Remove-MgOrganizationExtension -OrganizationId $OrganizationId -ExtensionId $ArcGISOpenExtensionId
            }

            Write-Host "All tenant-wide ArcGIS Connection settings have been reset." -ForegroundColor "Green"
        }
    }
}

Function Show-ArcGISConnections {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param()

    If ($PSCmdlet.ShouldProcess("List all ArcGIS connection configurations", $null, $null)) {
        if (Get-IsAuthenticated) {
            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {
                [Array]$ExtentionConfigurationArray = $Extension["connections"]

                $index = 1;
                foreach ($ExtentionConfigurationItem in $ExtentionConfigurationArray) {
                    Write-Host "`n#$index" -ForegroundColor  "Green"
                    if ($ExtentionConfigurationItem.isDefault -eq $true) {
                        Write-Host "[Default]" -ForegroundColor "Green"
                    }
                    Write-Host("Title: {0}" -f $ExtentionConfigurationItem.title) -ForegroundColor "Green"
                    Write-Host("URL: {0}" -f $ExtentionConfigurationItem.url) -ForegroundColor "Green"
                    $index = $index + 1
                }

                Write-Host("`n{0} ArcGISConnection(s) in total.`n" -f $ExtentionConfigurationArray.length) -ForegroundColor  "Green"
            }            
        }
    }

}

Function Export-ArcGISConnections {

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param()

    If ($PSCmdlet.ShouldProcess("Export all ArcGIS connection configurations to a file in the same directory.", $null, $null)) {
        if (Get-IsAuthenticated) {
            $Extension = Get-ArcGISExtension
            if ($null -ne $Extension) {
                [Array]$ExtentionConfigurationArray = $Extension["connections"]
                ConvertTo-Json $ExtentionConfigurationArray | Out-File "connections.txt"            
            }
        }
    }
}

# private Function
Function Get-ArcGISExtension {

    $OrganizationId = (Get-MgOrganization).Id

    $ExtensionIds = (Get-MgOrganizationExtension -OrganizationId $OrganizationId).Id

    if (($null -ne $ExtensionIds) -and $ExtensionIds.Contains($ArcGISOpenExtensionId)) {
        $Extension = Get-MgOrganizationExtension -OrganizationId $OrganizationId | Where-Object { $_.Id -eq $ArcGISOpenExtensionId }
        $NewExtension = @{
            "connections" = @()
            "disallowArcGISOnlineConnection" = -not(-not $Extension.AdditionalProperties["disallowArcGISOnlineConnection"])
            "disallowManualArcGISConnection" = -not(-not $Extension.AdditionalProperties["disallowManualArcGISConnection"])
        }

        $ExtentionConfiguration = $Extension.AdditionalProperties["connections"]
        try {
            $ExtentionConfigurationArray = ConvertFrom-Json $ExtentionConfiguration
            if ($ExtentionConfigurationArray -is [Array]) {
                $NewExtension["connections"] = $ExtentionConfigurationArray
            }
            return $NewExtension
        }
        catch {
            Write-Verbose $_
            return $NewExtension
        }
    }
    else {
        return $null
    }
}

# private Function
Function Test-ArcGISConnectionURL {
    Param
    (
        [Parameter(Mandatory = $true)]
        [String] $ArcGISConnectionUrl
    )

    if (-not ($ArcGISConnectionUrl -cmatch '(?i)(https:\/\/)([^\s,]+)')) {
        Throw "The specified ArcGISConnectionUrl parameter: '$ArcGISConnectionUrl' is not a valid URL string."
    }
    try {
        $Response = Invoke-WebRequest -URI "$ArcGISConnectionUrl/sharing/rest/portals/self?f=json" -TimeoutSec 5 -UseBasicParsing
        if ($Response.StatusCode -eq [HttpStatusCode]::OK) {
            return $true
        }
        else {
            Throw "The specified ArcGISConnectionUrl parameter: '$ArcGISConnectionUrl' is not a ArcGIS Online organization or ArcGIS Enterprise URL."
        }
    }
    catch {
        $WebException = $_.Exception;
        if ($WebException.Response.StatusCode -eq [HttpStatusCode]::Forbidden -or $WebException.Response.StatusCode -eq [HttpStatusCode]::Unauthorized) {
            return $true
        }
        else {
            Write-Host $_ -ForegroundColor  "Yellow"
            Throw "The specified ArcGISConnectionUrl parameter: '$ArcGISConnectionUrl' is not a ArcGIS Online organization or ArcGIS Enterprise URL."
        }
    }
}

#private function
Function Get-IsAuthenticated {
    $OrganizationId = (Get-MgOrganization).Id 2> $null
    if ($null -eq $OrganizationId) {
        Write-Host "Authentication needed, please call Connect-MgGraph -Scopes ""Organization.ReadWrite.All""." -ForegroundColor "Red"
        return $false
    }
    else {
        return $true
    }
}

Export-ModuleMember -Function New-ArcGISTermSet

Export-ModuleMember -Function New-ArcGISConnection
Export-ModuleMember -Function Update-ArcGISConnection
Export-ModuleMember -Function Set-CustomURLConnectionPermission
Export-ModuleMember -Function Set-ArcGISOnlineConnectionPermission
Export-ModuleMember -Function Remove-ArcGISConnection
Export-ModuleMember -Function Show-ArcGISConnections
Export-ModuleMember -Function Clear-AllTenantArcGISConnections
Export-ModuleMember -Function Reset-TenantArcGISSettings
Export-ModuleMember -Function Export-ArcGISConnections