Skip to content

Commit

Permalink
Big Update to ContextMenu.psm1
Browse files Browse the repository at this point in the history
  • Loading branch information
pldmgg authored Mar 6, 2024
1 parent 90cf562 commit a04ebb6
Showing 1 changed file with 308 additions and 19 deletions.
327 changes: 308 additions & 19 deletions MyModules/ContextMenu.psm1
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
function Get-Elevation {
if ($PSVersionTable.PSEdition -eq "Desktop" -or $PSVersionTable.Platform -eq "Win32NT" -or $PSVersionTable.PSVersion.Major -le 5) {
[System.Security.Principal.WindowsPrincipal]$currentPrincipal = New-Object System.Security.Principal.WindowsPrincipal(
[System.Security.Principal.WindowsIdentity]::GetCurrent()
)

[System.Security.Principal.WindowsBuiltInRole]$administratorsRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator

if($currentPrincipal.IsInRole($administratorsRole)) {
return $true
}
else {
return $false
}
}

if ($PSVersionTable.Platform -eq "Unix") {
if ($(whoami) -eq "root") {
return $true
}
else {
return $false
}
}
}

<#
.SYNOPSIS
Enables the new Windows 11 right-click context menu for all users
Expand Down Expand Up @@ -305,17 +331,17 @@ function Set-RegistryPermsForWin11ContextMenuModifications {
function Create-SPOLocalLinkContextMenu {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $False)]
[string]$ContextMenuName = "Copy Links To Clipboard",
[Parameter(Mandatory = $True)]
[string]$ContextMenuName, # "Copy Links To Clipboard"

[Parameter(Mandatory = $False)]
[string]$OnlineRootFolder = "$HOME\CompanyName\SharePointSite - Documents\",
[Parameter(Mandatory = $True)]
[string]$OnlineRootFolder, # "$HOME\CompanyName\SharePointSite - Documents\"

[Parameter(Mandatory = $False)]
[string]$SharePointBaseUrl = "https://companyname.sharepoint.com/sites/sharepointsite/Documents/Forms/AllItems.aspx?id=%2Fsites%2FSharePointSite%2FDocuments",
[Parameter(Mandatory = $True)]
[string]$SharePointBaseUrl, # "https://companyname.sharepoint.com/sites/sharepointsite/Documents/Forms/AllItems.aspx?id=%2Fsites%2FSharePointSite%2FDocuments"

[Parameter(Mandatory = $False)]
[string]$LibraryFolderID = '6495bcd2-cd10-4968-becf-c5cab8033e5a'
[Parameter(Mandatory = $True)]
[string]$LibraryFolderID # '6495bcd2-cd10-4968-becf-c5cab8033e5a'
)

$GetLinksScriptDir = "C:\Scripts\powershell"
Expand All @@ -330,9 +356,11 @@ function Create-SPOLocalLinkContextMenu {
$registryPathForFile = "Registry::HKEY_CLASSES_ROOT\*\shell\$ContextMenuName"
$registryPathForDir = "Registry::HKEY_CLASSES_ROOT\Directory\shell\$ContextMenuName"
$registryPathForDirBack = "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\$ContextMenuName"
$commandPathForFile = "$registryPathForFile\\command"
$commandPathForDir = "$registryPathForDir\\command"
$commandPathForDirBack = "$registryPathForDirBack\\command"

# Make sure $OnlineRootFolder does not expand $HOME
if ($OnlineRootFolder -match [regex]::Escape($HOME)) {
$OnlineRootFolder = $OnlineRootFolder -replace [regex]::Escape($HOME),'$HOME'
}

# Create the Get-SPOAndLocalLinks.ps1 script
$GetLinksScriptContent = @'
Expand Down Expand Up @@ -422,7 +450,7 @@ $RunHiddenExePath powershell.exe -File ""$GetLinksScriptPath"" ""%1""
#if (-not (Test-Path $registryPathForDirBack)) {$null = New-Item -Path $registryPathForDirBack -Force}
& reg add "HKCR\Directory\Background\shell\$ContextMenuName" /f

Write-Host "Adding Registry keys for $ContextMenuName ..."
Write-Host "Adding Registry keys for $ContextMenuName \command ..."
#if (-not (Test-Path $commandPathForFile)) {$null = New-Item -Path $commandPathForFile -Force}
#& reg add "HKCR\*\shell\$ContextMenuName\command" /f
& reg add "HKCR\*\shell\$ContextMenuName\command" /t REG_SZ /d "$commandForRegAdd" /f
Expand All @@ -436,11 +464,272 @@ $RunHiddenExePath powershell.exe -File ""$GetLinksScriptPath"" ""%1""
Write-Error $_
return
}
}

# Set the registry "command" key to invoke the PowerShell script with the selected file path
#Set-ItemProperty -Path $commandPathForFile -Name "(Default)" -Value $command -PropertyType String -Force
#Write-Host "Adding Registry keys for $ContextMenuName ..."
#& reg add "HKCR\*\shell\$ContextMenuName\command" /t REG_SZ /d "$commandForRegAdd" /f
#& reg add "HKCR\Directory\shell\$ContextMenuName\command" /t REG_SZ /d "$commandForRegAdd" /f
#& reg add "HKCR\Directory\Background\shell\$ContextMenuName\command" /t REG_SZ /d "$commandForRegAdd" /f
}

<#
.SYNOPSIS
Creates a context menu item for copying SharePoint Online and local file links to the clipboard
.DESCRIPTION
##### BEGIN Create/Register New App in Azure #####
# In Azure Dashboard, follow these steps:
<#
1) Navigate to https://portal.azure.com/
2) Hamburger menu -> Micrsoft Entra ID (formerly Azure Active Directory) -> App registrations -> Select "All applications" to see everything -> New registration
3) Fill out "Name" field -> Under "Supported account types" select "Accounts in this organizational directory only (Company only - Single tenant)"
4) Under "Redirect URI (optional)" use the "Select a platform" dropdown and choose "Single-page Application (SPA)" and URI field should be "https://login.live.com/oauth20_desktop.srf"
5) Take note: "Application (client) ID" = $AppClientID | "Directory (tenant)" = $TenantID
6) In the left-hand menu, click "API Permissions" -> Click "Add a permission" -> Click "Microsoft Graph" -> Click "Delegated permissions" ->
In the "Select permissions" Search field search for and add the following permissions:
- Files.Read
- Files.Read.All
- Files.Read.Selected
- Files.ReadWrite
- Files.ReadWrite.All
- Files.ReadWrite.AppFolder
- Files.ReadWrite.Selected
- Sites.Read.All
- Sites.ReadWrite.All
- User.Read
- User.ReadWrite
7) Do the same for "Application permissions" as in step 6, i.e. "API Permissions" -> Click "Add a permission" -> Click "Microsoft Graph" -> Click "Application permissions" ->
In the "Select permissions" Search field search for and add the following permissions:
- Files.Read.All
- Files.ReadWrite.All
- Sites.Read.All
- Sites.ReadWrite.All
- User.Read.All
- User.ReadWrite.All
8) Grant Admin Consent for all of the above permissions by clicking "Grant admin consent for <Org Name>"
9) In the left-hand menu, click on "Manifest" -> Ctrl + F for "oauth2AllowIdTokenImplicitFlow" and set to "true ->
Ctrl + F for "oauth2AllowImplicitFlow" and set to "true" so that it looks like the following:
"oauth2AllowIdTokenImplicitFlow": true,
"oauth2AllowImplicitFlow": true,
10) In the left-hand menu, click on "Authentication" -> Delete the SPA Platform entry -> Click "Add a platform" ->
Select "Mobile and desktop applications" -> Check the checkbox for URI "https://login.live.com/oauth20_desktop.srf" -> Click Save
11) If you want the App itself (as opposed to the logged-in user) to have the ability to upload files to SharePoint do the following:
- On the left-hand menu, click on "Certificates & secrets" -> Click "New client secret" ->
Take not that "Value" = $AppClientSecretValue
- On the left-hand menu, click "API Permissions" -> Click "Add a permission" -> Click "Microsoft Graph" ->
Click "Application permissions" -> Search for "Site.ReadWrite.All" -> Select the checkbox and click "Add permissions"
##### END Create/Register New App in Azure #####
##### BEGIN Setup Certificate Based Authentication #####
# If you haven't done so already, create a new self-signed certificate and upload it to your OneDriveAPI App in Azure
# Create a new self-signed certificate
# Find existing Veeam365App certificates installed in the Windows certificate Store on
Get-ChildItem -Path "Cert:\" -Recurse -Force | Where-Object {-not $_.PSIsContainer -and $_.Subject -match "OneDriveAPI"}
# First find the existing registered app here: https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview
# Generate App Certificate Instructions...
$certname = "CompanyOneDriveAPIApp2024"
$cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256
# Export Public Cert
Export-Certificate -Cert $cert -FilePath "$HOME\Downloads\$certname.cer"
# Export .pfx which contains Public Cert AND Private Key
$mypwd = ConvertTo-SecureString -String "mypasswd!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "$HOME\Downloads\$certname.pfx" -Password $mypwd
# Double-Click the resulting .pfx file and install it to "Cert:\LocalMachine\My" (it is already under "Cert:\CurrentUser\My" after New-SelfSignedCertificate commmand)
# Now upload .cer to Azure under your App's "Certificates and Secrets"
# Take Note of the Thumbprint of certificate via:
(Get-ChildItem -Path "Cert:\" -Recurse -Force | Where-Object {-not $_.PSIsContainer -and $_.Subject -match "OneDriveAPI"}).Thumbprint | Get-Unique
##### END Setup Certificate Based Authentication #####
.NOTES
DEPENDENCEIES
- run-hidden.exe (https://github.com/stax76/run-hidden) because we don't want to see the PowerShell window when the script runs
.EXAMPLE
Create-SPOLocalLinkContextMenu
.INPUTS
Inputs to this cmdlet (if any)
.OUTPUTS
Output from this cmdlet (if any)
#>
function Create-SyncWithOneDriveContextMenu {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)]
[string]$tenantId,

[Parameter(Mandatory = $True)]
[string]$clientId,

[Parameter(Mandatory = $True)]
[string]$certificateThumbprint,

[Parameter(Mandatory = $True)]
[string]$siteName,

[Parameter(Mandatory = $True)]
[string]$TargetDocumentLibrary,

[Parameter(Mandatory = $True)]
[string]$LocalDirEquivalentToDocumentLibraryRoot,

[Parameter(Mandatory = $False)]
[string]$ContextMenuName = "SyncWithOneDrive",

[Parameter(Mandatory = $False)]
[string]$pfxFilePath
)

# Install the Microsoft Graph PowerShell SDK
#Install-Module Microsoft.Graph -AllowClobber -Force -Confirm:$False
$ModuleName = 'Microsoft.Graph'
if (!$(Get-Module -ListAvailable $ModuleName -ErrorAction SilentlyContinue)) {
try {
$InstallModuleResult = Install-Module $ModuleName -AllowClobber -Force -ErrorAction Stop -WarningAction SilentlyContinue
# WARNING: Don't try to import the entire module...it takes like 5 minutes
# Instead just use the cmdlets directly and PowerShell will import specifically what you need from the Module at that time
#Import-Module Microsoft.Graph
# Alternate Module Install Method
<#
Set-PSRepository PSGallery -InstallationPolicy Trusted -ErrorAction Stop
Save-Module -Name $ModuleName -Path "$env:ProgramFiles\WindowsPowerShell\Modules" -Force -Confirm:$False -ErrorAction Stop
#>
} catch {
Write-Warning $_.Exception.Message
Write-Error "Unable to install $ModuleName module! Halting!"
return
}
}

$BinDir = "C:\Scripts\bin"
$RunHiddenExePath = "$BinDir\run-hidden.exe"
$RunHiddenZipPath = "$BinDir\run-hidden.zip"
$UploadFileToSPOScriptDir = 'C:\Scripts\powershell'
$UploadFileToSPOScriptPath = "$UploadFileToSPOScriptDir\Upload-FileToSPOViaMSGraphPSSDK.ps1"
$CertsDir = "C:\Scripts\certs"
if (-NOT $(Test-Path $UploadFileToSPOScriptDir)) {$null = New-Item -Path $UploadFileToSPOScriptDir -ItemType Directory -Force}
if (-NOT $(Test-Path $BinDir)) {$null = New-Item -Path $BinDir -ItemType Directory -Force}
if (-NOT $(Test-Path $CertsDir)) {$null = New-Item -Path $CertsDir -ItemType Directory -Force}

$PSRegistryCommand = @"
$RunHiddenExePath powershell.exe -File "$UploadFileToSPOScriptPath" "%1"
"@

# Make sure the run-hidden.exe is installed
if (!(Test-Path $RunHiddenExePath)) {
#Write-Error "run-hidden.exe not found! Halting!"
#return
Write-Host "Downloading run-hidden.exe ..."
$null = Invoke-WebRequest -Uri "https://github.com/stax76/run-hidden/releases/download/v1.2/run-hidden-v1.2.zip" -OutFile $RunHiddenZipPath
$null = Expand-Archive -Path $RunHiddenZipPath -DestinationPath $BinDir -Force
}

if ($PSVersionTable.Platform -ne 'Win32NT' -and $PSVersionTable.PSEdition -ne 'Desktop') {
Write-Error "Only run the {0} function from a Windows operating system! Halting!" -f $MyInvocation.MyCommand
$global:FunctionResult = "1"
return
}

if (!$(Get-Elevation)) {
Write-Error "You must run this script/function as Administrator! Halting!"
$global:FunctionResult = 1
return
}

# Install the OneDriveAPI Azure App Certificate in the Windows Certificate Store If it's not already there
# Then search Windows Certificate Store for the Thumbprint
$CurrentUserMyCheck = Get-ChildItem -Path "Cert:\CurrentUser\My" | Where-Object {$_.Thumbprint -eq $certificateThumbprint}
$LocalMachineMyCheck = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object {$_.Thumbprint -eq $certificateThumbprint}
# Load it into the certificate store if it doesn't already exist
if (!$CurrentUserMyCheck -or !$LocalMachineMyCheck) {
$PfxFilePwdSS = Read-Host -Prompt 'Enter password for .pfx file' -AsSecureString
}
if (!$CurrentUserMyCheck) {
# Import the .pfx to the Current User's Personal Store
$null = Import-PfxCertificate -FilePath $pfxFilePath -CertStoreLocation Cert:\CurrentUser\My -Password $PfxFilePwdSS
}
if (!$LocalMachineMyCheck) {
# Import the .pfx to the Local Machine's Personal Store
$null = Import-PfxCertificate -FilePath $pfxFilePath -CertStoreLocation Cert:\LocalMachine\My -Password $PfxFilePwdSS
}

# Add the registry keys for the context menu entry
$RegistryPathsToCheck = @(
'Registry::HKEY_CLASSES_ROOT\*\shell\{0}\command' -f $ContextMenuName
'Registry::HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\{0}\command' -f $ContextMenuName
)
foreach ($RegPath in $RegistryPathsToCheck) {
Push-Location -PSPath 'Registry::HKEY_CLASSES_ROOT\*\'
$RelativePath = $RegPath -replace 'Registry::HKEY_CLASSES_ROOT\\\*','.'

if (!$(Test-Path -PSPath $RegPath)) {
$null = New-Item -Path $RelativePath -Force -ErrorAction Stop
}

# Check the (Default) String/REG_SZ values for the "command" key
$RegistryItem = Get-Item $RelativePath
$RegItemDefaultValue = $($RegistryItem | Get-ItemProperty).'(default)'
if ($RegItemDefaultValue -ne $PSRegistryCommand) {
# Update the (Default) String/REG_SZ values for the "command" keys
$null = Set-Item -Path $RelativePath -Value $PSRegistryCommand
}

Pop-Location
}

# Create the Upload-FileToSPOViaMSGraphPSSDK.ps1.ps1 script
$UploadFileToSPOViaMSGraphPSSDKScriptContent = @'
param (
[string[]]$LocalFilesToUpload
)
'@ + @"
# Get your OneDriveAPI App's ClientID and TenantID from the Azure Dashboard
`$tenantId = '$tenantId'
`$clientId = '$clientId'
`$certificateThumbprint = '$certificateThumbprint'
`$siteName = '$siteName'
`$TargetDocumentLibrary = '$TargetDocumentLibrary'
`$LocalDirEquivalentToDocumentLibraryRoot = '$LocalDirEquivalentToDocumentLibraryRoot'
"@ + @'
# Connect to Microsoft Graph
# The below is non-interactive and uses a certificate
Connect-MgGraph -TenantId $tenantId -ClientId $clientId -CertificateThumbprint $certificateThumbprint -NoWelcome
# Get $siteId and $driveId
$SiteInfo = Get-MgSite -Search $sitename
$SiteId = $SiteInfo.Id
$DocumentLibraries = @(Get-MgSiteDrive -SiteId $SiteId | Where-Object {$_.DriveType -eq "documentLibrary"})
# SIDE NOTE: Review Site Collection Storage via $DocumentLibraries.Quota
# Get the specific document libary you want to upload to
$LibraryItem = $DocumentLibraries | Where-Object {$_.Name -eq $TargetDocumentLibrary}
# Fetch DriveId for the specific Document Library
$driveId = $LibraryItem.Id
# Upload a file to the Document Library
# IMPORTANT NOTE: The below $LocalFilesToUpload is pulled from the "%1" in the registry command key value, i.e.
# "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoExit -File "C:\Scripts\powershell\Upload-FileToSPOViaMSgraphPSSDK.ps1" "%1"
foreach ($FilePath in $LocalFilesToUpload) {
# Make sure $FilePath actually exists
if (!$(Test-Path -Path $FilePath)) {
Write-Error "$FilePath does not exist! Check the path and try again. Halting!"
return
}
# IMPORTANT NOTE: The below Get-Content actually downloads the file to the local hard drive if it's not already,
# so this is a necessary step to prevent scenario where we try to upload a file that is not actually on localhost
$null = Get-Content -Path $FilePath
$FileItemToUpload = Get-Item -Path $FilePath
$FileToUpload = $FileItemToUpload.FullName
$FileName = $FileItemToUpload.Name
$DirPath = $FilePath -replace [regex]::Escape($LocalDirEquivalentToDocumentLibraryRoot),'' -replace $FileName,'' -replace '\\','/'
$UploadUrl = 'https://graph.microsoft.com/v1.0/sites/' + $siteId + '/drives/' + $driveId + '/root:' + $DirPath + $FileName + ':/content'
#https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}/root:/ProjectDocuments/Report.pdf:/content
$FileContent = Get-Content -Path $FileToUpload -Raw
Invoke-MgGraphRequest -Uri $UploadUrl -Method PUT -Body $FileContent -ContentType "text/plain"
}
'@

Write-Host "Creating $UploadFileToSPOScriptPath ..."
$UploadFileToSPOViaMSGraphPSSDKScriptContent | Out-File -FilePath $UploadFileToSPOScriptPath -Force

}

0 comments on commit a04ebb6

Please sign in to comment.