In a previous post, I covered how to properly secure files using 7-Zip with password protection before sharing them with users. While that solved the security aspect, I was still facing a significant workflow bottleneck: the manual process of uploading files to OneDrive and generating shareable links.
Process Workflow
- Preparing and password-protecting ZIP files with 7-Zip
- Manually logging into OneDrive through the web interface
- Uploading each file individually
- Generating a shareable link for each file
- Copying and organizing these links for distribution
This process was time-consuming and prone to human error, especially when dealing with multiple customer requests simultaneously.
Visual Output : Professional E-mail
This shows the output of the e-mail, which is simple and easy to follow, the actual OneDrive links can be obtained with a right click on the button and "copy link"
The API Limitation
Initially, I explored using Microsoft's Graph API for automation. However, I quickly discovered a significant limitation: only OneDrive business accounts have full API access. For personal OneDrive accounts, API functionality is severely restricted.
To make matters more complex, our organizational policies prohibited creating public links through business accounts for security reasons. This left me in a challenging position - needing automation capabilities that weren't readily available through standard Microsoft channels.
rclone: Remote Connection
This is where rclone became my solution. rclone is a command-line program that can manage files on cloud storage services, and crucially, it can work with OneDrive personal accounts despite the API limitations.
I set up a OneDrive personal account that mimics my work email address, which provides a professional appearance while maintaining the necessary functionality. Since the OneDrive public links don't reveal account ownership details, recipients only see clean, professional sharing links without any personal information exposure.
When you have installed rclone, extract it to a valid folder I used c:\rclone then can then see the transcript below which you can follow:
if ($successCount -gt 0) {
rclone config
NOTICE: Config file "c:\rclone\rclone.conf" not found - using defaults
No remotes found, make a new one?
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n
Enter name for new remote.
name> onedrive
Storge > 38
Option client_id.
OAuth Client Id.
Leave blank normally.
Enter a value. Press Enter to leave empty.
client_id>
Option client_secret.
OAuth Client Secret.
Leave blank normally.
Enter a value. Press Enter to leave empty.
client_secret>
Option region.
Choose national cloud region for OneDrive.
Choose a number from below, or type in your own value of type string.
Press Enter for the default (global).
1 / Microsoft Cloud Global
\ (global)
2 / Microsoft Cloud for US Government
\ (us)
3 / Microsoft Cloud Germany (deprecated - try global region first).
\ (de)
4 / Azure and Office 365 operated by Vnet Group in China
\ (cn)
region> 1
Option tenant.
ID of the service principal's tenant. Also called its directory ID.
Set this if using
- Client Credential flow
Enter a value. Press Enter to leave empty.
tenant>
Edit advanced config?
y) Yes
n) No (default)
y/n> n
Use web browser to automatically authenticate rclone with remote?
* Say Y if the machine running rclone has a web browser you can use
* Say N if running rclone on a (remote) machine without web browser access
If not sure try Y. If Y failed, try N.
y) Yes (default)
n) No
y/n> y
2025/08/19 17:11:06 NOTICE: Make sure your Redirect URL is set to "http://localhost:53682/" in your custom config.
2025/08/19 17:11:06 NOTICE: If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=Tz2yoIIlBymxMo-0WVHNUQ
2025/08/19 17:11:06 NOTICE: Log in and authorize rclone for access
2025/08/19 17:11:06 NOTICE: Waiting for code...
2025/08/19 17:11:42 NOTICE: Got code
Option config_type.
Type of connection
Choose a number from below, or type in an existing value of type string.
Press Enter for the default (onedrive).
1 / OneDrive Personal or Business
\ (onedrive)
2 / Root Sharepoint site
\ (sharepoint)
/ Sharepoint site name or URL
3 | E.g. mysite or https://contoso.sharepoint.com/sites/mysite
\ (url)
4 / Search for a Sharepoint site
\ (search)
5 / Type in driveID (advanced)
\ (driveid)
6 / Type in SiteID (advanced)
\ (siteid)
/ Sharepoint server-relative path (advanced)
7 | E.g. /teams/hr
\ (path)
config_type> 1
Option config_driveid.
Select drive you want to use
Choose a number from below, or type in your own value of type string.
Press Enter for the default (E2FA22059BA3D08C).
1 / OneDrive (personal)
\ (E2FA22059BA3D08C)
2 / ODCMetadataArchive (personal)
\ (b!QzAgfJ0wsUKz6mbDw2edLfNo3pbvx91Km6s8qNl-AFvlpZUGhjCQT4SBfAZRhaFU)
3 / AEEE102E-CFF8-4E2A-89C6-03841FF83500 (personal)
\ (b!QzAgfJ0wsUKz6mbDw2edLfNo3pbvx91Km6s8qNl-AFui-fqSUBqmRqi5zdA7Vpz4)
config_driveid> 1
Drive OK?
Found drive "root" of type "personal"
URL: https://onedrive.live.com?cid=<data>
y) Yes (default)
n) No
y/n> y
Configuration complete.
Building the Automation: PowerShell Scripts
I developed two PowerShell scripts to completely automate my workflow:
Script 1: Upload and Link Generation
The first script handles the heavy lifting:
- Scans a designated folder for ZIP files following the required naming convention
- Uploads each file to OneDrive using rclone
- Generates shareable links for each uploaded file
- Extracts customer names from filenames and formats them cleanly
- Outputs a text file with the correct format
The script includes proper error handling, proxy support for corporate environments, and progress reporting.
Script 2: Professional Email Distribution
The second script takes the generated links and creates professional HTML emails:
- Reads the links file generated by the first script
- Creates beautifully formatted HTML emails with proper styling
- Organizes links in a clean table format showing customer names and access buttons
- Sends emails via SMTP without requiring authentication
- Includes professional messaging explaining the secure, read-only access
The Results: From Hours to Minutes
What previously took me 30-45 minutes of manual work now completes in under 5 minutes:
Before:
- Manual login to OneDrive
- Individual file uploads (with progress watching)
- Manual link generation for each file
- Copy-pasting links into emails
- Manual email composition and sending
After:
- Run the upload script (handles multiple files automatically)
- Run the email script (sends professional HTML emails)
- Total hands-on time: ~2 minutes
# OneDrive Upload and Link Generator Script
# Uploads ZIP files from source folder to OneDrive and generates shareable links
param(
[string]$SourceFolder = "c:\Uploads",
[string]$OutputFile = "Links.txt",
[string]$OneDriveRemotePath = "onedrive:/Upload",
[string]$RclonePath = "C:\rclone\rclone.exe",
[string]$ProxyServer = "squid.bear.local:3129"
)
# Clear the output file if it exists
if (Test-Path $OutputFile) {
Clear-Content $OutputFile
}
# Set proxy environment variables if proxy is specified
if ($ProxyServer -and $ProxyServer -ne "") {
$env:http_proxy = "http://$ProxyServer"
$env:https_proxy = "http://$ProxyServer"
$env:HTTP_PROXY = "http://$ProxyServer"
$env:HTTPS_PROXY = "http://$ProxyServer"
Write-Host "Proxy environment variables set" -ForegroundColor Green
}
Write-Host "Starting OneDrive upload process..." -ForegroundColor Green
Write-Host "Source folder: $SourceFolder" -ForegroundColor Yellow
Write-Host "OneDrive path: $OneDriveRemotePath" -ForegroundColor Yellow
Write-Host "Output file: $OutputFile" -ForegroundColor Yellow
Write-Host "Rclone path: $RclonePath" -ForegroundColor Yellow
Write-Host "Proxy server: $ProxyServer" -ForegroundColor Yellow
Write-Host ""
# Check if rclone exists
if (-not (Test-Path $RclonePath)) {
Write-Error "Rclone not found at: $RclonePath"
Write-Host "Please check the rclone installation path." -ForegroundColor Red
exit 1
}
# Check if source folder exists
if (-not (Test-Path $SourceFolder)) {
Write-Error "Source folder does not exist: $SourceFolder"
exit 1
}
# Get all ZIP files from the source folder
$zipFiles = Get-ChildItem -Path $SourceFolder -Filter "*.zip"
if ($zipFiles.Count -eq 0) {
Write-Warning "No ZIP files found in the source folder."
exit 0
}
Write-Host "Found $($zipFiles.Count) ZIP file(s) to process:" -ForegroundColor Cyan
foreach ($file in $zipFiles) {
Write-Host " - $($file.Name)" -ForegroundColor Gray
}
Write-Host ""
$successCount = 0
$errorCount = 0
# Process each ZIP file
foreach ($zipFile in $zipFiles) {
Write-Host "Processing: $($zipFile.Name)" -ForegroundColor White
try {
# Upload file to OneDrive using rclone
Write-Host " Uploading..." -ForegroundColor Yellow -NoNewline
$uploadResult = & $RclonePath copy $zipFile.FullName $OneDriveRemotePath 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host " ✓ Success" -ForegroundColor Green
# Generate shareable link
Write-Host " Generating link..." -ForegroundColor Yellow -NoNewline
$linkResult = & $RclonePath link "$OneDriveRemotePath/$($zipFile.Name)" 2>&1
if ($LASTEXITCODE -eq 0) {
# Filter out NOTICE messages and extract just the URL
$cleanOutput = $linkResult | Where-Object {
$_ -notmatch "NOTICE:" -and
$_ -notmatch "Don't know how to convert" -and
$_ -match "^https://"
}
$shareUrl = $cleanOutput | Select-Object -First 1
if ($shareUrl) {
Write-Host " ✓ Success" -ForegroundColor Green
# Extract name from filename (remove SARC number and .zip extension)
# Pattern: SARC1007400 - Brady.zip -> Brady
if ($zipFile.Name -match "SARC\d+\s*-\s*(.+)\.zip$") {
$extractedName = $matches[1].Trim()
} else {
# Fallback: just remove .zip extension
$extractedName = $zipFile.BaseName
}
# Format: Brady - <onedrive link>
$outputLine = "$extractedName - $shareUrl"
# Append to output file
Add-Content -Path $OutputFile -Value $outputLine
Write-Host " Link saved: $extractedName" -ForegroundColor Green
$successCount++
} else {
Write-Host " ✗ No valid URL found" -ForegroundColor Red
Write-Host " Output: $linkResult" -ForegroundColor Red
$errorCount++
}
} else {
Write-Host " ✗ Failed to generate link" -ForegroundColor Red
Write-Host " Error: $linkResult" -ForegroundColor Red
$errorCount++
}
} else {
Write-Host " ✗ Upload failed" -ForegroundColor Red
Write-Host " Error: $uploadResult" -ForegroundColor Red
$errorCount++
}
}
catch {
Write-Host " ✗ Exception occurred" -ForegroundColor Red
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
$errorCount++
}
Write-Host ""
}
# Summary
Write-Host "=== SUMMARY ===" -ForegroundColor Magenta
Write-Host "Files processed: $($zipFiles.Count)" -ForegroundColor White
Write-Host "Successful: $successCount" -ForegroundColor Green
Write-Host "Errors: $errorCount" -ForegroundColor Red
if ($successCount -gt 0) {
Write-Host "Links saved to: $OutputFile" -ForegroundColor Green
Write-Host ""
Write-Host "Contents of ${OutputFile}:" -ForegroundColor Cyan
Get-Content $OutputFile | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
}
Write-Host ""
Write-Host "Process completed!" -ForegroundColor Green