ConvertFrom-OpensearchItemId.ps1

<#PSScriptInfo
 
.VERSION 2023.12.6
 
.GUID dd297485-342b-4999-8c56-e9e969edb01a
 
.AUTHOR Kent Sapp (@cksapp)
 
.COMPANYNAME
 
.COPYRIGHT © 2023 | Kent Sapp
 
.TAGS Datto-Internal, OpenSearch
 
.LICENSEURI https://gist.github.com/cksapp/2a46f9b4342877b62586ff2f99931fc1#file-license
 
.PROJECTURI https://gist.github.com/cksapp/2a46f9b4342877b62586ff2f99931fc1
 
.ICONURI https://gist.github.com/assets/66083310/3e673212-80b1-4fbd-a0c7-2d489a378dbc
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES Lots of changes here, better handling of errors and URL validation. Looking good for Release Candidate!
 
 
.PRIVATEDATA
 
 
#>


<#
.SYNOPSIS
    1. Modifies item IDs by replacing '-' with '+' and '_' with '/'
    2. Converts the modified item ID to URL encoded format
    3. Generates URL links for the modified item IDs
 
.DESCRIPTION
    This is an interactive script that modifies ItemId(s) to a URL encoded format and generates a Microsoft link to the affected item.
 
.NOTES
    This function is designed to modify item IDs from Datto OpenSearch and provide a link to the Outlook Web App calendar URL.
    This is a PowerShell adaptation for the original `EWSreplacer.bat` from @Liam Cowan for the PT [#4315344](https://kaseya.zendesk.com/agent/tickets/4315344)
#>



function Get-OutlookUrl {
    [CmdletBinding()]
    param (
        [Parameter(
            Position = 0, 
            Mandatory = $true, 
            ValueFromPipeline = $true, 
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $opensearchItemId
    )

    process {
        if ($opensearchItemId -eq 'q') {
            return 'q'
        }
        Write-Verbose -Message "Processing OpensearchItemId: $opensearchItemId"

        # Step 1: Replaces '-' with '+' and '_' with '/'
        $originalMsItemId = $opensearchItemId -replace '-', '+' -replace '_', '/'
        Write-Verbose -Message "Step 1 - Microsoft ItemID: $originalMsItemId"

        # Add Type System.Web if not already added
        Try {
            [System.Web.HttpUtility]$HttpUtilityTest
        } Catch {
            Add-Type -AssemblyName System.Web
        }
        # Step 2: Convert to URL encoded format
        $urlEncodedItemId = [System.Web.HttpUtility]::UrlEncode($originalMsItemId)
        Write-Verbose -Message "Step 2 - URL Encoded: $urlEncodedItemId"

        # Step 3: Generate OWA calendar URL link
        [System.Uri]$owaUrl = "https://outlook.office.com/calendar/item/$urlEncodedItemId"
        Write-Verbose -Message "Step 3 - Converted URL: $owaUrl"


        ## Back to the validation step here not working, was not testing against powershell 5.
        ## Some logic added to handle the exceptions seen.

        # Validate URL link created
        if ([string]::IsNullOrWhiteSpace($doUrlValidation) -or $doUrlValidation -eq $true) {
            try {
                $response = Invoke-WebRequest -Uri $owaUrl -UseBasicParsing -ErrorAction SilentlyContinue

                if ($response.StatusCode -eq 200) {
                    Write-Verbose -Message "Validation for $($owaUrl.AbsoluteUri) successful."
                    #Write-Host "Validation for $($owaUrl.AbsoluteUri) successful."
                }
            } catch {
                # Handling for error (417) when validation run on PowerShell 5.1
                if ($_.Exception.Message -notlike '*The remote server returned an error: (417).*') {
                    $script:invalidUrl = $true  # Set the flag to true for invalid URLs
                    [string]$script:urlValidationError = "$($_.Exception.Message)"

                    Write-Host "$($_.Exception.Message)" -ForegroundColor Red
                    Write-Host "Output URL validation appears to have encountered an error. Check the original input `"" -ForegroundColor Yellow -NoNewLine
                    Write-Host "$opensearchItemId" -ForegroundColor Gray -NoNewLine
                    Write-Host "`" and try again." -ForegroundColor Yellow
                }
            }
        }


        # Cast the URI back to a string type and return the variable
        [String]$owaLink = $owaUrl.AbsoluteUri
        $owaLink
    }
}

function Start-Counter {
    param (
        $countdownSeconds = 5
    )

    # Countdown
    for ($i = $countdownSeconds; $i -ge 1; $i--) {
        Write-Host "Exit in $i seconds..."
        Start-Sleep -Seconds 1
    }
}

# URL Validation choice user input
$urlValidationChoice = $(Write-Host "Warning: This can add additional processing and time on significantly larger datasets`n" -ForegroundColor yellow -NoNewLine) + $(Write-Host "Attempt URL validation? (Select, Y/yes or N/no): " -NoNewLine; Read-Host)

# Enable URL validation check, takes in user input and converts to boolean value
if ($urlValidationChoice.ToLower() -eq 'y' -or $urlValidationChoice -eq 'yes') {
    $doUrlValidation = $true
}


# Script portion to allow for user selection
$sourceMethod = Read-Host "Select your input method (Type: 1 for Console, 2 for CSV file) then `"Enter`""

switch ($sourceMethod) {
    1 {
        do {
            $opensearchItemId = Read-Host "Enter itemID(s) from OpenSearch (or type 'q' to quit)"
            $convertedUrl = Get-OutlookUrl -opensearchItemId $opensearchItemId

            if ($convertedUrl -ne 'q') {
                Write-Host "URL Link: " -ForegroundColor Green -NoNewLine
                Write-Host "$($convertedUrl)" -ForegroundColor Cyan
            }

        } while ($convertedUrl -ne 'q')
        break
    }

    2 {
        # Get user input for CSV file path
        $csvFilePath = Read-Host "Enter the path to the CSV file"
        $csvFilePath = $csvFilePath -replace '"', '' # Remove any quotes from the file path if found


        # Import CSV file and filter the data for just ItemIds
        try {
            $sourceCsvData = Import-Csv -Path $csvFilePath -Delimiter ','
            Write-Verbose -Message "File successfully imported."

            # Check if the file extension is .csv
            $fileType = (Get-Item $csvFilePath).Extension
            if ($fileType -ne '.csv') {
                throw "Error: The file `"$((Get-Item $csvFilePath).Name)`" does not appear to be a CSV. Check the input and try again."
            }

            # Filter CSV data to include only columns with labels containing "ItemId"
            $csvItemIds = $sourceCsvData | ForEach-Object {
                $_.PSObject.Properties | Where-Object { $_.Name -like 'ItemId*' } | ForEach-Object {
                    $_.Value
                }
            }

            # Check if any columns with labels containing "ItemId" were found
            if (-not $csvItemIds) {
                throw "Error: No header found containing 'ItemId' in the CSV file. Check the formatting and try again."
            }

        } catch {
            $csvImportError = $_.Exception.Message
            Write-Host "$csvImportError" -ForegroundColor Red

            Read-Host -Prompt "Press any key to continue"
            Start-Counter; break
        }



        # Array to store results
        $convertedCsvArray = [System.Collections.ArrayList]::new()

        # Convert each $csvItemIds and put them back into the array
        foreach ($opensearchItemId in $csvItemIds) {
            Write-Host "Processing CSV ItemId: $opensearchItemId"

            # Convert the Item Ids and get the results
            [string]$convertedUrl = Get-OutlookUrl -opensearchItemId $opensearchItemId


            # Create a custom object with the "Output_Url" property
            $outputResult = [PSCustomObject]@{
                "Output_Url" = $convertedUrl
            }

            # Check for valid URL, outputs error if invalid URL seen
            # If $invalidUrl was detected then set flag for invalid URL detected, and add custom error message to $outputResult
            if ($invalidUrl) {
                $invalidUrlDetected = $true

                $outputErrorMessage = "Check the input ItemID or the URL output, and try again. Failed URL Validation with: `"$urlValidationError`""
                Add-Member -InputObject $outputResult -MemberType NoteProperty -Name "Output_Error" -Value "$outputErrorMessage"
                #Add-Member -InputObject $outputResult -MemberType NoteProperty -Name "Output_Error" -Value "Invalid output URL detected. Check the input ItemId: $opensearchItemId"
                Add-Member -InputObject $outputResult -MemberType NoteProperty -Name "Input_ItemID" -Value "$opensearchItemId"
            }

            # Add the output result to the ArrayList
            $convertedCsvArray.Add($outputResult)


            Write-Host "Converted URL Link: " -ForegroundColor Green -NoNewLine
            Write-Host "$convertedUrl" -ForegroundColor Cyan

            # Clean up the $invalidUrl variable if seen
            $invalidUrl = $null
        }



        # Get parent directory, and filename from the input CSV file path
        $csvDirectory = (Get-Item $csvFilePath).Directory.FullName
        $csvFileName = (Get-Item $csvFilePath).BaseName
        $outputCsvPath = Join-Path -Path $csvDirectory -ChildPath "$csvFileName-Converted_Urls.csv"

        # Check the flag after processing the URLs. Exports results to a new CSV file in the source directory
        if ($invalidUrlDetected) {
            Write-Host "One or more invalid URLs were found. Be sure to validate the output." -ForegroundColor Red -BackgroundColor DarkYellow
            $convertedCsvArray | Select-Object -Property Output_Url,Output_Error,Input_ItemID | Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8
        } else {
            Write-Host "Validation Passed: URL output appears to be valid" -ForegroundColor Gray -BackgroundColor DarkGreen
            $convertedCsvArray | Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8
        }

        Write-Host "Results exported to: $outputCsvPath"
        $openFileExport = Read-Host -Prompt "Do you want to open the file? (Select, Y/yes or N/no)"
        
        if ($openFileExport.ToLower() -eq 'y' -or $openFileExport -eq 'yes') {
            Invoke-Item $outputCsvPath
        }
        Exit
        #Start-Counter; break
    }
    
    default {
        # Default switch, catch any invalid user input and exit
        Write-Host "Invalid choice. Exiting the script." -BackgroundColor Red

        Start-Counter
        break
    }
}