ConvertFrom-OpensearchItemId.ps1
<#PSScriptInfo
.VERSION 2023.12.8 .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, user input will now work for both yes and no options and default to checking validation if null input. Also added script update prompt function. Looking good for first 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 } } } elseif ($doUrlValidation -eq $false) { Write-Verbose "Output Validation Disabled: Skipping URL Check" } # Cast the URI back to a string type and return the variable [String]$owaLink = $owaUrl.AbsoluteUri $owaLink } } function Update-PSScriptInteractive { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True )] [String] $scriptName ) process { Write-Host "Checking for any updates to the script..." -ForegroundColor Black -BackgroundColor White # Check if the Script is installed $installedScript = Get-InstalledScript $scriptName -ErrorAction SilentlyContinue $onlineScript = Find-Script -Name $scriptName -ErrorAction SilentlyContinue if (!$installedScript) { try { Write-Host "Script `"$scriptName`" does not appear to be installed, attempting to install." -ForegroundColor Yellow Install-Script $scriptName -Scope "CurrentUser" -ErrorAction Stop -Verbose #-Force Write-Host "Script `"$scriptName`" successfully installed." -ForegroundColor Green } catch { Write-Error "Error installing Script $scriptName`: $_" throw } } else { Write-Verbose "Script $scriptName is already installed on the local system." # Prompt to update the Script if the online version seen is higher if ($installedScript.version -eq $onlineScript.version) { Write-Host "$scriptName version installed is $($installedScript.version) which matches the online version $($onlineScript.version)`n" -ForegroundColor Green } elseif ($installedScript.version -gt $onlineScript.version) { Write-Host "$scriptName version installed is $($installedScript.version) which is greater than the online version $($onlineScript.version)`nStrange, but okay I guess?`n" -ForegroundColor Gray } elseif ($installedScript.version -lt $onlineScript.version) { Write-Host "$scriptName version installed is $($installedScript.version) which is less than the online version $($onlineScript.version)" -ForegroundColor DarkYellow -BackgroundColor DarkGray $upgradeScriptChoice = Read-Host -Prompt "Would you like to update now? (Select, Y/yes or N/no)" if ([string]::IsNullOrWhiteSpace($upgradeScriptChoice) -or $upgradeScriptChoice.ToLower() -eq 'y' -or $upgradeScriptChoice -eq 'yes') { Write-Host "Updating $scriptName from version $($installedScript.version) to $($onlineScript.version)." Update-Script -Name $scriptName -Force Start-Process -FilePath "$(Join-Path -Path $($installedScript.InstalledLocation) -ChildPath ($scriptName)).ps1" <#-WindowStyle Normal#>; Exit } Write-Host # Empty line for formatting } } } } function Start-Counter { param ( $countdownSeconds = 5 ) # Countdown for ($i = $countdownSeconds; $i -ge 1; $i--) { Write-Host "Exit in $i seconds..." Start-Sleep -Seconds 1 } } # Checks for update to script and prompt user if available Update-PSScriptInteractive -scriptName "ConvertFrom-OpensearchItemId" # 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 ([string]::IsNullOrWhiteSpace($urlValidationChoice) -or $urlValidationChoice.ToLower() -eq 'y' -or $urlValidationChoice.ToLower() -eq 'yes') { $doUrlValidation = $true } elseif ($urlValidationChoice.ToLower() -eq 'n' -or $urlValidationChoice.ToLower() -eq 'no') { $doUrlValidation = $false } Write-Host # Empty line for formatting # 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)" $outlookUrl = Get-OutlookUrl -opensearchItemId $opensearchItemId $convertedUrl = ([string]$outlookUrl).trim() # Remove any leading/trailing whitespaces from the string if ($opensearchItemId -ne 'q') { Write-Host "Output URL: " -ForegroundColor Green -NoNewLine Write-Host "$convertedUrl" -ForegroundColor Cyan } } while ($opensearchItemId -ne 'q') #} until ($opensearchItemId -eq 'q') Start-Counter; 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 exit the script." 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) { $invalidUrl = $null # Clean up the $invalidUrl variable if detected from previous loop # Convert the Item Ids and get the results Write-Host "Processing CSV ItemId: $opensearchItemId" $outlookUrl = Get-OutlookUrl -opensearchItemId $opensearchItemId $convertedUrl = ([string]$outlookUrl).trim() # Remove any leading/trailing whitespaces from the string # Create a custom object with the "Output_Url" property $outputResult = [PSCustomObject]@{ "Output_Url" = "$convertedUrl" } # Check for valid ItemId input, outputs error if 'q' quit detected if ($opensearchItemId -eq 'q') { $outputErrorMessage = "Invalid Input ItemId: `"$opensearchItemId`" found. Check the input and try again." Write-Host "$outputErrorMessage" -ForegroundColor Yellow Add-Member -InputObject $outputResult -MemberType NoteProperty -Name "Output_Error" -Value "$outputErrorMessage" Add-Member -InputObject $outputResult -MemberType NoteProperty -Name "Input_ItemID" -Value "$opensearchItemId" } # 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 "Input_ItemID" -Value "$opensearchItemId" } # Add the output result to the ArrayList $convertedCsvArray.Add($outputResult) Write-Host "Output URL: " -ForegroundColor Green -NoNewLine Write-Host "$convertedUrl" -ForegroundColor Cyan } # 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 ($doUrlValidation -and $invalidUrlDetected) { Write-Host "One or more invalid URLs were detected. Be sure to validate the output." -ForegroundColor Red -BackgroundColor Yellow $convertedCsvArray | Select-Object -Property Output_Url,Output_Error,Input_ItemID | Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8 } elseif ($doUrlValidation -and !$invalidUrlDetected) { Write-Host "Validation Passed: URL output appears to be valid" -ForegroundColor Gray -BackgroundColor DarkGreen $convertedCsvArray | Select-Object -Property Output_Url| Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8 } elseif (!$doUrlValidation) { Write-Host "URL Validation Disabled: Skipped Output Check" -ForegroundColor DarkYellow -BackgroundColor DarkGray $convertedCsvArray | Select-Object -Property Output_Url| Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8 } Write-Host "Results exported to: " -NoNewLine Write-Host "$outputCsvPath" -ForegroundColor DarkGray $openFileExport = Read-Host -Prompt "Do you want to open the file? (Select, Y/yes or N/no)" if ($openFileExport.ToLower() -eq 'y' -or $openFileExport.ToLower() -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." -ForegroundColor Yellow -BackgroundColor Red Start-Counter; break } } |