ConvertFrom-OpensearchItemId.ps1
<#PSScriptInfo
.VERSION 2023.12.10 .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 Adds console export options, CSV and console output. Better calls for running the script after auto-update. .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" $opensearchItemId = $($opensearchItemId).trim() # 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 -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 $scriptPath = "$(Join-Path -Path $($installedScript.InstalledLocation) -ChildPath ($scriptName)).ps1" if ($PSVersionTable.PSVersion.Major -ge 6) { Write-Host "Starting the updated script." ; Start-Process pwsh -ArgumentList "-File `"$scriptPath`""; Exit # PowerShell Core and later } else { Write-Host "Starting the updated script." ; Start-Process powershell -ArgumentList "-File `"$scriptPath`""; Exit # Windows PowerShell 5.1 } #Write-Host "Starting the updated script." ; & $scriptPath; 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 { # Array to store console output results $consoleOutputResults = New-Object System.Collections.ArrayList do { $invalidUrl = $null # Clean up the $invalidUrl variable if detected from previous loop $opensearchItemId = Read-Host "Enter itemID(s) from OpenSearch (or type 'q' to quit)" if (![string]::IsNullOrWhiteSpace($opensearchItemId) -and $opensearchItemId -ne 'q') { $outlookUrl = Get-OutlookUrl -opensearchItemId $opensearchItemId $convertedUrl = ([string]$outlookUrl).trim() # Remove any leading/trailing whitespaces from the string Write-Host "Output URL: " -ForegroundColor Green -NoNewLine; Write-Host "$convertedUrl" -ForegroundColor Cyan # 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 "Input_ItemID" -Value "$opensearchItemId" } # Add the results to the ArrayList $consoleOutputResults.Add($outputResult) | Out-Null # Out-Null suppresses the output of Add method } } while ($opensearchItemId -ne 'q') #} until ($opensearchItemId -eq 'q') # Check the flag after processing the URLs if ($doUrlValidation -and $invalidUrlDetected) { Write-Host "One or more invalid URLs were detected. Be sure to validate the output." -ForegroundColor Red -BackgroundColor Yellow } elseif ($doUrlValidation -and !$invalidUrlDetected) { Write-Host "Validation Passed: URL output appears to be valid" -ForegroundColor Gray -BackgroundColor DarkGreen } elseif (!$doUrlValidation) { Write-Host "URL Validation Disabled: Skipped Output Check" -ForegroundColor DarkYellow -BackgroundColor DarkGray } # Prompt the user for CSV export if the console output is greater than 1 if ($consoleOutputResults.Count -gt 1) { Write-Host # Empty line for formatting $exportConsoleChoice = Read-Host -Prompt "Would you like to export the output to the Console or a CSV? (Type: 1 for Console, 2 for CSV file)`nEnter anything else to skip" switch ($exportConsoleChoice) { 1 { $($consoleOutputResults.Output_Url) | Format-Table; Write-Host # Empty line for formatting try { Set-Clipboard -Value $($consoleOutputResults.Output_Url) Write-Host "Output successfully copied to clipboard!" -ForegroundColor Green } catch { Write-Host "Error copying to clipboard: " -NoNewLine; Write-Host "$_" -ForegroundColor Yellow } Read-Host -Prompt "Press `"Enter`" to exit" | Out-Null #Write-Host "Press any key to exit." -NoNewLine; [void][System.Console]::ReadKey($false) } 2 { $outputCsvPath = Join-Path -Path $($HOME) -ChildPath "$(Get-Date -UFormat "%Y-%m-%dT%T%Z" | ForEach-Object { $_ -replace ":", "." })-Console_Converted_Urls.csv" # Export CSV properties depending on errors detected if ($doUrlValidation -and $invalidUrlDetected) { $consoleOutputResults | Select-Object -Property Output_Url,Output_Error,Input_ItemID | Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8 } elseif (!$doUrlValidation -or $doUrlValidation -and !$invalidUrlDetected) { $consoleOutputResults | Select-Object -Property Output_Url| Export-Csv -Path $outputCsvPath -NoTypeInformation -Delimiter ',' -Encoding UTF8 } Write-Host "Console results exported to CSV: " -ForegroundColor Green -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 } } Default { break } } } Start-Counter -countdownSeconds 3; 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." | Out-Null 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) | Out-Null 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 } } |