DSCResources/ArcGIS_AzureSetupDownloadsFolderManager/ArcGIS_AzureSetupDownloadsFolderManager.psm1

function Get-TargetResource
{
    param(
        [Parameter(Mandatory=$True)]
        [System.String]
        $Version,

        [Parameter(Mandatory=$True)]
        [System.String]
        $OperationType,
       
        [Parameter(Mandatory=$True)]
        [System.String]
        $ComponentNames,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole,

        [Parameter(Mandatory=$false)]
        [System.String]
        $UpgradeSetupsSourceFileSharePath,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        $UpgradeSetupsSourceFileShareCredentials,

        [Parameter(Mandatory=$false)]
        [System.Array]
        $AdditionalFilesOrFolderToDelete
    )

    $null

}

function Set-TargetResource
{
    param(
        [Parameter(Mandatory=$True)]
        [System.String]
        $Version,

        [Parameter(Mandatory=$True)]
        [System.String]
        $OperationType,
       
        [Parameter(Mandatory=$True)]
        [System.String]
        $ComponentNames,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole,

        [Parameter(Mandatory=$false)]
        [System.String]
        $UpgradeSetupsSourceFileSharePath,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        $UpgradeSetupsSourceFileShareCredentials,

        [Parameter(Mandatory=$false)]
        [System.Array]
        $AdditionalFilesOrFolderToDelete
    )

    $SetupsStagingFolderPath = "C:\ArcGIS\Deployment\Downloads"
    if($OperationType -ieq "CleanupDownloadsFolder"){
        # Cleanup the downloads folder
        Invoke-DownloadFolderCleanup -ComponentNames $ComponentNames -ServerRole $ServerRole -SetupsStagingFolderPath $SetupsStagingFolderPath -Verbose
    }elseif($OperationType -ieq "DownloadUpgradeSetups"){
        $ComponentNamesArray = $ComponentNames -split ","
        if($ComponentNamesArray.Length -gt 1){
            throw "Multiple components are not supported for this operation."
        }
        
        Invoke-DownloadSetupFromUpgradeVM -Version $Version -ComponentName ($ComponentNames) -ServerRole $ServerRole `
                                    -UpgradeSetupsSourceFileSharePath $UpgradeSetupsSourceFileSharePath -UpgradeSetupsSourceFileShareCredentials $UpgradeSetupsSourceFileShareCredentials `
                                    -SetupsStagingFolderPath $SetupsStagingFolderPath -Verbose
    }else{
        throw "OperationType '$OperationType' is not supported."
    }

    if($AdditionalFilesOrFolderToDelete -ne $null){
        Invoke-AdditionalFolderOrFilesToCleanup -FileOrFolderPaths $AdditionalFilesOrFolderToDelete -Verbose
    }
    
}

function Test-TargetResource
{
    param(
        [Parameter(Mandatory=$True)]
        [System.String]
        $Version,

        [Parameter(Mandatory=$True)]
        [System.String]
        $OperationType,
       
        [Parameter(Mandatory=$True)]
        [System.String]
        $ComponentNames,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole,

        [Parameter(Mandatory=$false)]
        [System.String]
        $UpgradeSetupsSourceFileSharePath,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        $UpgradeSetupsSourceFileShareCredentials,

        [Parameter(Mandatory=$false)]
        [System.Array]
        $AdditionalFilesOrFolderToDelete
    )

    $false
}

function Add-LongPathPrefix {
    param(
        [string]$Path
    )
    if ($Path.Length -gt 259) {
        if (-not $Path.StartsWith("\\\\?\\")) {
            return "\\\\?\\" + $Path
        } else {
            return $Path
        }
     } else {
        return $Path
     }
}

function Invoke-AdditionalFolderOrFilesToCleanup
{
    param(
        [Parameter(Mandatory=$false)]
        [System.Array]
        $FileOrFolderPaths
    )

    foreach($FileOrFolderPath in $FileOrFolderPaths){
        if(Test-Path -Path $FileOrFolderPath) {
            Write-Verbose "Found file/folder: $FileOrFolderPath"
            try {
                # Check if the path is a file or folder
                if (Test-Path -Path $FileOrFolderPath -PathType Leaf) {
                    $FilePathToDelete = Add-LongPathPrefix $FileOrFolderPath
                    Remove-Item -Path $FilePathToDelete -Force 
                    Write-Verbose "Deleted file: $FileOrFolderPath" 
                } elseif (Test-Path -Path $FileOrFolderPath -PathType Container) {
                    $FolderPathToDelete = Add-LongPathPrefix $FileOrFolderPath
                    Remove-Item -Path $FolderPathToDelete -Recurse -Force 
                    Write-Verbose "Deleted file/folder: $FileOrFolderPath" 
                }
            } catch {
                Write-Verbose "[WARNING] Error deleting folder '$($FileOrFolderPath)' - $($_.Exception.Message)"
                # Swallow the error and continue
                # This is to ensure that the script continues even if there are errors in deleting some files/folders
                continue
            }
        } else {
            Write-Verbose "File/Folder not found: $FileOrFolderPath"
        }
    }
}

function  Invoke-DownloadFolderCleanup {
    param(
        [Parameter(Mandatory=$false)]
        [System.String]
        $ComponentNames,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole,

        [Parameter(Mandatory=$false)]
        [System.String]
        $SetupsStagingFolderPath
    )

    $ComponentNamesArray = $ComponentNames -split ","
    if(Test-Path -Path $SetupsStagingFolderPath) {
        $ExceptionFolders = @()
        if($ComponentNamesArray -icontains "Portal"){
            $ExceptionFolders += @("$($SetupsStagingFolderPath)\Portal","$($SetupsStagingFolderPath)\WebStyles")
        }
        if($ComponentNamesArray -icontains "DataStore"){
            $ExceptionFolders += @("$($SetupsStagingFolderPath)\DataStore")
        }
        if($ComponentNamesArray -icontains "Server"){
            if($ServerRole -ieq "NotebookServer"){
                $ExceptionFolders += @("$($SetupsStagingFolderPath)\NotebookServer","$($SetupsStagingFolderPath)\WebAdaptorIIS")
            }elseif($ServerRole -ieq "MissionServer"){
                $ExceptionFolders += @("$($SetupsStagingFolderPath)\MissionServer")
            }elseif($ServerRole -ieq "VideoServer"){
                $ExceptionFolders += @("$($SetupsStagingFolderPath)\VideoServer")
            }else{
                $ExceptionFolders += @("$($SetupsStagingFolderPath)\Server","$($SetupsStagingFolderPath)\SQLNativeClient")

                if($ServerRole -ieq "GeoEventServer"){
                    $ExceptionFolders += @("$($SetupsStagingFolderPath)\GeoEvent")
                }
                if($ServerRole -ieq "WorkflowManagerServer"){
                    $ExceptionFolders += @("$($SetupsStagingFolderPath)\WorkflowManagerServer")
                }
                $ExceptionFolders += "$($SetupsStagingFolderPath)\Server"
            }
        }

        # Is all is one deploy
        $Subfolders = Get-ChildItem -Path $SetupsStagingFolderPath -Directory
        foreach ($Folder in $Subfolders) {
            $FolderPath = $folder.FullName
    
            # Check if folder exists
            if (Test-Path -Path $FolderPath) {
                Write-Verbose "Found folder: $FolderPath"
                if($FolderPath -in $ExceptionFolders) {
                    Write-Verbose "Skipped deleting folder: $FolderPath"
                } else {
                    try {
                        Remove-Item -Path $FolderPath -Recurse -Force 
                        Write-Verbose "Deleted folder: $FolderPath" 
                    } catch {
                        Write-Verbose "Error deleting folder: $FolderPath. Error: $_"
                        continue
                    }
                }
            } else {
                Write-Verbose "Folder not found: $FolderPath"
            }
        }
    }else{
        Write-Verbose "Folder $DownloadsRoot not found. No cleanup needed."
    }
}
function Invoke-DownloadSetupFromUpgradeVM
{
    param(
        [Parameter(Mandatory=$false)]
        [System.String]
        $Version,    
        
        [Parameter(Mandatory=$false)]
        [System.String]
        $UpgradeVMName,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ComponentName,

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole,

        [Parameter(Mandatory=$false)]
        [System.String]
        $UpgradeSetupsSourceFileSharePath,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]
        $UpgradeSetupsSourceFileShareCredentials,

        [Parameter(Mandatory=$false)]
        [System.String]
        $SetupsStagingFolderPath
    )

    # Make sure the staging path exists
    $StagingPath = "$($SetupsStagingFolderPath)\$($Version)"
    if (-not(Test-Path -Path $StagingPath)) {
        New-Item -Path $StagingPath -ItemType Directory -Force | Out-Null
    }

    # if notebook, check if web adaptor is currently installed, if not then skip the download
    $TempDriveName = "X"
    try{
        if (Get-PSDrive $TempDriveName -ErrorAction SilentlyContinue) {
            Remove-PSDrive -Name $TempDriveName -Force -ErrorAction SilentlyContinue
        } 
        Write-Verbose "Mapping drive '$TempDriveName' to '$($UpgradeSetupsSourceFileSharePath)'"
        $PsDrive = New-PsDrive -Name $TempDriveName -Root $UpgradeSetupsSourceFileSharePath -PSProvider FileSystem -Credential $UpgradeSetupsSourceFileShareCredentials
        Write-Verbose "Mapped drive '$TempDriveName' to '$($UpgradeSetupsSourceFileSharePath)'"
        if($ComponentName -ieq "Portal"){
            # Copy portal setup
            $PortalUpgradeSetupPath = "$($PsDrive.Name):\Portal\PortalforArcGIS.exe"
            $PortalUpgradeSetupDestinationPath = "$($StagingPath)\PortalforArcGIS.exe"
            if(-not(Test-Path -Path $PortalUpgradeSetupDestinationPath)){
                if(-not(Test-Path $PortalUpgradeSetupPath)){
                    throw "Required Portal setup file was not found."
                }
                Copy-Item -Path $PortalUpgradeSetupPath -Destination $PortalUpgradeSetupDestinationPath -Force
                Write-Verbose "Copied Portal setup from '$PortalUpgradeSetupPath' to '$PortalUpgradeSetupDestinationPath'"
            }else{
                Write-Verbose "Portal setup already exists at '$PortalUpgradeSetupDestinationPath'"
            }

            # Copy the portal setup volume
            $PortalUpgradeSetupVolumePath = "$($PsDrive.Name):\Portal\PortalforArcGIS.exe.001"
            $PortalUpgradeSetupVolumeDestinationPath = "$($StagingPath)\PortalforArcGIS.exe.001"
            if(-not(Test-Path -Path $PortalUpgradeSetupVolumeDestinationPath)){
                if(-not(Test-Path $PortalUpgradeSetupVolumePath)){
                    throw "Required Portal setup volume was not found."
                }
                Copy-Item -Path $PortalUpgradeSetupVolumePath -Destination $PortalUpgradeSetupVolumeDestinationPath -Force
                Write-Verbose "Copied Portal setup volume from '$PortalUpgradeSetupVolumePath' to '$PortalUpgradeSetupVolumeDestinationPath'"
            }else{
                Write-Verbose "Portal setup volume already exists at '$PortalUpgradeSetupVolumeDestinationPath'"
            }

            # Copy webstyles
            $WebStylesUpgradeSetupPath = "$($PsDrive.Name):\WebStyles\WebStyles.exe"
            $WebStylesUpgradeSetupDestinationPath = "$($StagingPath)\WebStyles.exe"
            if(-not(Test-Path -Path $WebStylesUpgradeSetupDestinationPath)){
                if(-not(Test-Path $WebStylesUpgradeSetupPath)){
                    throw "Required Web Styles setup file was not found."
                }
                Copy-Item -Path $WebStylesUpgradeSetupPath -Destination $WebStylesUpgradeSetupDestinationPath -Force
                Write-Verbose "Copied Web Styles setup from '$WebStylesUpgradeSetupPath' to '$WebStylesUpgradeSetupDestinationPath'"
            }else{
                Write-Verbose "Web Styles setup already exists at '$WebStylesUpgradeSetupDestinationPath'"
            }
        }elseif($ComponentName -ieq "DataStore"){
            # Copy datastore setup
            $DSUpgradeSetupPath = "$($PsDrive.Name):\DataStore\DataStore.exe"
            $DSUpgradeSetupDestinationPath = "$($StagingPath)\DataStore.exe"
            if(-not(Test-Path -Path $DSUpgradeSetupDestinationPath)){
                if(-not(Test-Path $DSUpgradeSetupPath)){
                    throw "Required DataStore setup file was not found."
                }
                Copy-Item -Path $DSUpgradeSetupPath -Destination $DSUpgradeSetupDestinationPath -Force
                Write-Verbose "Copied DataStore setup from '$DSUpgradeSetupPath' to '$DSUpgradeSetupDestinationPath'"
            }else{
                Write-Verbose "DataStore setup already exists at '$DSUpgradeSetupDestinationPath'"
            }
        }elseif($ComponentName -ieq "Server"){
            if($ServerRole -ieq "NotebookServer"){
                # Copy notebook server setup
                $NotebookUpgradeSetupPath = "$($PsDrive.Name):\NotebookServer\NotebookServer.exe"
                $NotebookUpgradeSetupDestinationPath = "$($StagingPath)\NotebookServer.exe"
                if(-not(Test-Path -Path $NotebookUpgradeSetupDestinationPath)){
                    if(-not(Test-Path $NotebookUpgradeSetupPath)){
                        throw "Required Notebook Server setup file was not found."
                    }
                    Copy-Item -Path $NotebookUpgradeSetupPath -Destination $NotebookUpgradeSetupDestinationPath -Force
                    Write-Verbose "Copied Notebook Server setup from '$NotebookUpgradeSetupPath' to '$NotebookUpgradeSetupDestinationPath'"
                }else{
                    Write-Verbose "Notebook Server setup already exists at '$NotebookUpgradeSetupDestinationPath'"
                }

                # TODO: Check if single machine deployment or not
                # Copy WebAdaptor setup
                $WebAdaptorUpgradeSetupPath = "$($PsDrive.Name):\WebAdaptorIIS\WebAdaptorIIS.exe"
                $WebAdaptorUpgradeSetupDestinationPath = "$($StagingPath)\WebAdaptorIIS.exe"
                if(-not(Test-Path -Path $WebAdaptorUpgradeSetupDestinationPath)){
                    if(-not(Test-Path $WebAdaptorUpgradeSetupPath)){
                        throw "Required Web Adaptor setup file was not found."
                    }
                    Copy-Item -Path $WebAdaptorUpgradeSetupPath -Destination $WebAdaptorUpgradeSetupDestinationPath -Force
                    Write-Verbose "Copied Web Adaptor setup from '$WebAdaptorUpgradeSetupPath' to '$WebAdaptorUpgradeSetupDestinationPath'"
                }else{
                    Write-Verbose "Web Adaptor setup already exists at '$WebAdaptorUpgradeSetupDestinationPath'"
                }
                
                $WAAdditionFilesPath = "$($PsDrive.Name):\WebAdaptorIIS\AdditionalFiles"
                if(-not(Test-Path $WAAdditionFilesPath)){
                    throw "Required additional files for Web Adaptor were not found at $WAAdditionFilesPath"
                }

                # Copy WebDeploy setup
                $WebDeployInstallerPath = Get-ChildItem -Path $WAAdditionFilesPath -Filter "*WebDeploy*" -Recurse | Select-Object -ExpandProperty FullName
                if([string]::IsNullOrEmpty($WebDeployInstallerPath)){
                    throw "Required Web Deploy file for Web Adaptor was not found at $WAAdditionFilesPath"
                }
                $WebDeployInstallerDestinationPath = "$($StagingPath)\WebDeploy_amd64_en-US.msi"
                Copy-Item -Path $WebDeployInstallerPath -Destination $WebDeployInstallerDestinationPath -Force -Verbose

                # Copy hosting bundle setup
                $DotnetHostingBundleInstallerPath = Get-ChildItem -Path $WAAdditionFilesPath -Filter "*dotnet-hosting*" -Recurse | Select-Object -ExpandProperty FullName
                if([string]::IsNullOrEmpty($DotnetHostingBundleInstallerPath)){
                    throw "Required dotnet-hosting bundle file for Web Adaptor was not found at $WAAdditionFilesPath"
                }
                $DotnetHostingBundleDestinationPath = "$($StagingPath)\dotnet-hosting-win.exe"
                Copy-Item -Path $DotnetHostingBundleInstallerPath -Destination $DotnetHostingBundleDestinationPath -Force -Verbose

                # Copy notebook container images to the local machine
                $NBAdditionFilesPath = "$($PsDrive.Name):\NotebookServer\AdditionalFiles"
                $NotebookContainerPath = Get-ChildItem -Path $NBAdditionFilesPath -Filter "*arcgis-notebook-python-windows-$($Version)*" -Recurse | Select-Object -ExpandProperty FullName
                if([string]::IsNullOrEmpty($NotebookContainerPath)){
                    Write-Verbose "Required Notebook container image file for Notebook Server was not found at $NBAdditionFilesPath"
                }else{
                    $NotebookContainerDestinationPath = "$($StagingPath)\arcgis-notebook-python-windows-$($Version).tar.gz"
                    Copy-Item -Path $NotebookContainerPath -Destination $NotebookContainerDestinationPath -Force -Verbose
                }

            }elseif($ServerRole -ieq "MissionServer"){
                # Copy mission server setup
                $MissionUpgradeSetupPath = "$($PsDrive.Name):\MissionServer\MissionServer.exe"
                $MissionUpgradeSetupDestinationPath = "$($StagingPath)\MissionServer.exe"
                if(-not(Test-Path -Path $MissionUpgradeSetupDestinationPath)){
                    if(-not(Test-Path $MissionUpgradeSetupPath)){
                        throw "Required Mission Server setup file was not found."
                    }
                    Copy-Item -Path $MissionUpgradeSetupPath -Destination $MissionUpgradeSetupDestinationPath -Force
                    Write-Verbose "Copied Mission Server setup from '$MissionUpgradeSetupPath' to '$MissionUpgradeSetupDestinationPath'"
                }else{
                    Write-Verbose "Mission Server setup already exists at '$MissionUpgradeSetupDestinationPath'"
                }
            }else{
                # Copy Server setup
                $ServerUpgradeSetupPath = "$($PsDrive.Name):\Server\ArcGISforServer.exe"
                $ServerUpgradeSetupDestinationPath = "$($StagingPath)\ArcGISforServer.exe"
                if(-not(Test-Path -Path $ServerUpgradeSetupDestinationPath)){
                    if(-not(Test-Path $ServerUpgradeSetupPath)){
                        throw "Required Server setup file was not found."
                    }
                    Copy-Item -Path $ServerUpgradeSetupPath -Destination $ServerUpgradeSetupDestinationPath -Force
                    Write-Verbose "Copied Server setup from '$ServerUpgradeSetupPath' to '$ServerUpgradeSetupDestinationPath'"
                }else{
                    Write-Verbose "Server setup already exists at '$ServerUpgradeSetupDestinationPath'"
                }
                # Copy the server setup volume
                $ServerUpgradeSetupVolumePath = "$($PsDrive.Name):\Server\ArcGISforServer.exe.001"
                $ServerUpgradeSetupVolumeDestinationPath = "$($StagingPath)\ArcGISforServer.exe.001"
                if(-not(Test-Path -Path $ServerUpgradeSetupVolumeDestinationPath)){
                    if(-not(Test-Path $ServerUpgradeSetupVolumePath)){
                        throw "Required Server setup volume was not found."
                    }
                    Copy-Item -Path $ServerUpgradeSetupVolumePath -Destination $ServerUpgradeSetupVolumeDestinationPath -Force
                    Write-Verbose "Copied Server setup volume from '$ServerUpgradeSetupVolumePath' to '$ServerUpgradeSetupVolumeDestinationPath'"
                }else{
                    Write-Verbose "Server setup volume already exists at '$ServerUpgradeSetupVolumeDestinationPath'"
                }
                if($ServerRole -ieq "GeoEventServer"){
                    # Copy GeoEvent server setup
                    $GeoEventUpgradeSetupPath = "$($PsDrive.Name):\GeoEvent\GeoEvent.exe"
                    $GeoEventUpgradeSetupDestinationPath = "$($StagingPath)\GeoEvent.exe"
                    if(-not(Test-Path -Path $GeoEventUpgradeSetupDestinationPath)){
                        if(-not(Test-Path $GeoEventUpgradeSetupPath)){
                            throw "Required GeoEvent Server setup file was not found."
                        }
                        Copy-Item -Path $GeoEventUpgradeSetupPath -Destination $GeoEventUpgradeSetupDestinationPath -Force
                        Write-Verbose "Copied GeoEvent Server setup from '$GeoEventUpgradeSetupPath' to '$GeoEventUpgradeSetupDestinationPath'"
                    }else{
                        Write-Verbose "GeoEvent Server setup already exists at '$GeoEventUpgradeSetupDestinationPath'"
                    }
                }elseif($ServerRole -ieq "WorkflowManagerServer"){
                    # Copy Workflow Manager server setup
                    $WorkflowManagerServerUpgradeSetupPath = "$($PsDrive.Name):\WorkflowManagerServer\WorkflowManagerServer.exe"
                    $WorkflowManagerServerUpgradeSetupDestinationPath = "$($StagingPath)\WorkflowManagerServer.exe"
                    if(-not(Test-Path -Path $WorkflowManagerServerUpgradeSetupDestinationPath)){
                        if(-not(Test-Path $WorkflowManagerServerUpgradeSetupPath)){
                            throw "Required Workflow Manager Server setup file was not found."
                        }
                        Copy-Item -Path $WorkflowManagerServerUpgradeSetupPath -Destination $WorkflowManagerServerUpgradeSetupDestinationPath -Force
                        Write-Verbose "Copied Workflow Manager Server setup from '$WorkflowManagerServerUpgradeSetupPath' to '$WorkflowManagerServerUpgradeSetupDestinationPath'"
                    }else{
                        Write-Verbose "Workflow Manager Server setup already exists at '$WorkflowManagerServerUpgradeSetupDestinationPath'"
                    }
                }
            }
        }else{
            throw "ComponentName '$ComponentName' is not supported."
        }
    } catch {
        throw "Failed to copy required setups for '$($ComponentName)' from path '$($FileShareRootPath)'. Error: $($_.Exception.Message)"
    } finally {
        # Clean up: Remove the mapped drive when done
        if (Get-PSDrive $TempDriveName -ErrorAction SilentlyContinue) {
            Remove-PSDrive -Name $TempDriveName -Force
            Write-Verbose "Removed PSDrive '$TempDriveName'."
        }
    }
}

Export-ModuleMember -Function *-TargetResource