Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 107 additions & 46 deletions bdr-veeam/veeam-add-backup-repo.ps1
Original file line number Diff line number Diff line change
@@ -1,60 +1,130 @@
## Please note this script can only support the following backup repository types ##
# S3 Compabitble & Local. Both are forced.
# S3 Compatible & Local. Both are forced.

Comment on lines 1 to 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Missing RMM Variable Declaration Section per coding guidelines.

The script should begin with a comment block listing all required RMM variables. Based on the variables used (lines 50-61), add the documentation block:

 ## Please note this script can only support the following backup repository types ##
 # S3 Compatible & Local. Both are forced.

+## PLEASE COMMENT YOUR VARIABLES DIRECTLY BELOW HERE IF YOU'RE RUNNING FROM A RMM
+## $repositoryType - 1 for S3, 2 for Local, 3 for Both
+## $moveBackups - 1 to move all backups to local repository
+## $description - Description for audit trail
+## $immutabilityPeriod - Immutability period for S3 repository
+## $accessKey - S3 access key
+## $secretKey - S3 secret key (sensitive)
+## $endpoint - S3 endpoint URL
+## $regionId - S3 region ID
+## $bucketName - S3 bucket name
+## $driveLetters - Drive letters for WinLocal repos
+## $folderName - Folder name for repository
+## $moveListedBackups - Specific backups to move
+

As per coding guidelines, all PowerShell scripts must document required RMM variables in the top comment block.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Please note this script can only support the following backup repository types ##
# S3 Compabitble & Local. Both are forced.
# S3 Compatible & Local. Both are forced.
## Please note this script can only support the following backup repository types ##
# S3 Compatible & Local. Both are forced.
## PLEASE COMMENT YOUR VARIABLES DIRECTLY BELOW HERE IF YOU'RE RUNNING FROM A RMM
## $repositoryType - 1 for S3, 2 for Local, 3 for Both
## $moveBackups - 1 to move all backups to local repository
## $description - Description for audit trail
## $immutabilityPeriod - Immutability period for S3 repository
## $accessKey - S3 access key
## $secretKey - S3 secret key (sensitive)
## $endpoint - S3 endpoint URL
## $regionId - S3 region ID
## $bucketName - S3 bucket name
## $driveLetters - Drive letters for WinLocal repos
## $folderName - Folder name for repository
## $moveListedBackups - Specific backups to move
🤖 Prompt for AI Agents
In `@bdr-veeam/veeam-add-backup-repo.ps1` around lines 1 - 3, Add a top-of-file
comment block documenting all required RMM variables: list each variable
referenced later in the script (the variables used around lines 50-61 such as
those controlling repository type, bucket/name, endpoint, access key, secret,
region and any $RMM_* or $env:* variables the script reads) with a short
description and whether it’s required; insert this block above the existing
header comments so the script follows the coding guideline requiring RMM
variable declarations.

Start-Transcript -Path $env:WINDIR\logs\veeam-add-backup-repo.log
# ===========================================
# PowerShell 7 x64 Check, Install, and Bootstrap
# ===========================================

Write-Host "Checking if we are running from a RMM or not."
# MUST use x64 path - Veeam modules are 64-bit only
$ps7Path = "C:\Program Files\PowerShell\7\pwsh.exe"

# if ($rmm -ne 1) {
# Set the repository details
# $repositoryType = "Please enter the repository type (1 S3 Compatible; 2 Windows Local; 3 Both)"
# $moveBackups = "Enter 1 if you wish to move your local backups"
# $description = Read-Host "Please enter the ticket # or project ticket # related to this configuration"
# $immutabilityPeriod = Read-Host "Enter how many days every object is immutable for"
# $repositoryName = Read-Host "Enter the repository name" | Out-String
# $accessKey = Read-Host "Enter the access key"
# $secretKey = Read-Host "Enter the secret key"
# $endpoint = Read-Host "Enter the S3 endpoint url"
# $regionId = Read-Host "Enter the region ID"
# $bucketName = Read-Host "Enter the bucket name"
if ($PSVersionTable.PSVersion.Major -lt 7) {

Write-Host "Running in PowerShell $($PSVersionTable.PSVersion.Major) - checking for PS7 x64..."

# Check if PS7 x64 is installed
if (-not (Test-Path $ps7Path)) {
Write-Host "PowerShell 7 x64 not found. Installing..."

try {
$msiUrl = "https://github.com/PowerShell/PowerShell/releases/download/v7.4.7/PowerShell-7.4.7-win-x64.msi"
$msiPath = "$env:TEMP\PS7-x64.msi"

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath -UseBasicParsing

Write-Host "Downloaded PS7 installer. Running silent install..."
Start-Process msiexec.exe -ArgumentList "/i `"$msiPath`" /qn REGISTER_MANIFEST=1" -Wait -NoNewWindow

Remove-Item $msiPath -Force -ErrorAction SilentlyContinue

if (-not (Test-Path $ps7Path)) {
Write-Error "PowerShell 7 x64 installation failed"
exit 1
}
Write-Host "PowerShell 7 x64 installed successfully"
}
catch {
Write-Error "Failed to download/install PowerShell 7: $_"
exit 1
}
}

# ===========================================
# Capture NinjaRMM variables to pass to PS7
# ===========================================
Write-Host "Capturing RMM variables for PS7 session..."

# Build parameter string for PS7 relaunch
$scriptBlock = @"
`$repositoryType = '$repositoryType'
`$moveBackups = '$moveBackups'
`$description = '$description'
`$immutabilityPeriod = '$immutabilityPeriod'
`$accessKey = '$accessKey'
`$secretKey = '$secretKey'
`$endpoint = '$endpoint'
`$regionId = '$regionId'
`$bucketName = '$bucketName'
`$driveLetters = '$driveLetters'
`$folderName = '$folderName'
`$moveListedBackups = '$moveListedBackups'

"@
Comment on lines +49 to +63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

String interpolation vulnerability and secret exposure risk.

Two issues with the variable capture block:

  1. Injection/syntax error: If any variable contains a single quote (e.g., $description = "John's Backup"), the generated script will have invalid syntax: $description = 'John's Backup'

  2. Security: $secretKey is written in plain text to a temp file on disk (line 75-76), which could be read by other processes.

Proposed fix using escaped single quotes and SecureString
     $scriptBlock = @"
-`$repositoryType = '$repositoryType'
-`$moveBackups = '$moveBackups'
-`$description = '$description'
-`$immutabilityPeriod = '$immutabilityPeriod'
-`$accessKey = '$accessKey'
-`$secretKey = '$secretKey'
-`$endpoint = '$endpoint'
-`$regionId = '$regionId'
-`$bucketName = '$bucketName'
-`$driveLetters = '$driveLetters'
-`$folderName = '$folderName'
-`$moveListedBackups = '$moveListedBackups'
+`$repositoryType = '$($repositoryType -replace "'", "''")'
+`$moveBackups = '$($moveBackups -replace "'", "''")'
+`$description = '$($description -replace "'", "''")'
+`$immutabilityPeriod = '$($immutabilityPeriod -replace "'", "''")'
+`$accessKey = '$($accessKey -replace "'", "''")'
+`$secretKey = '$($secretKey -replace "'", "''")'
+`$endpoint = '$($endpoint -replace "'", "''")'
+`$regionId = '$($regionId -replace "'", "''")'
+`$bucketName = '$($bucketName -replace "'", "''")'
+`$driveLetters = '$($driveLetters -replace "'", "''")'
+`$folderName = '$($folderName -replace "'", "''")'
+`$moveListedBackups = '$($moveListedBackups -replace "'", "''")'

 "@

For the secret key exposure, consider restricting temp file permissions or using environment variables to pass sensitive data.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$scriptBlock = @"
`$repositoryType = '$repositoryType'
`$moveBackups = '$moveBackups'
`$description = '$description'
`$immutabilityPeriod = '$immutabilityPeriod'
`$accessKey = '$accessKey'
`$secretKey = '$secretKey'
`$endpoint = '$endpoint'
`$regionId = '$regionId'
`$bucketName = '$bucketName'
`$driveLetters = '$driveLetters'
`$folderName = '$folderName'
`$moveListedBackups = '$moveListedBackups'
"@
$scriptBlock = @"
`$repositoryType = '$($repositoryType -replace "'", "''")'
`$moveBackups = '$($moveBackups -replace "'", "''")'
`$description = '$($description -replace "'", "''")'
`$immutabilityPeriod = '$($immutabilityPeriod -replace "'", "''")'
`$accessKey = '$($accessKey -replace "'", "''")'
`$secretKey = '$($secretKey -replace "'", "''")'
`$endpoint = '$($endpoint -replace "'", "''")'
`$regionId = '$($regionId -replace "'", "''")'
`$bucketName = '$($bucketName -replace "'", "''")'
`$driveLetters = '$($driveLetters -replace "'", "''")'
`$folderName = '$($folderName -replace "'", "''")'
`$moveListedBackups = '$($moveListedBackups -replace "'", "''")'
"@
🤖 Prompt for AI Agents
In `@bdr-veeam/veeam-add-backup-repo.ps1` around lines 49 - 63, The here-string
construction in $scriptBlock naively injects variables (e.g., $description,
$bucketName, $folderName, $moveBackups) using single quotes which breaks when
values contain apostrophes and also writes $secretKey in plain text to a temp
file; fix by sanitizing/escaping single quotes for each interpolated value
before building $scriptBlock (e.g., replace ' with '\'' or use a serialization
method that preserves content), and stop writing the raw $secretKey to disk—pass
secrets as a SecureString or via an environment variable/secure credential
object or restrict temp file permissions and delete immediately if disk use is
unavoidable; update the code that constructs $scriptBlock and the logic that
writes/reads the temp file containing $secretKey accordingly.


# Read main script content (everything after the marker)
$scriptContent = Get-Content -Path $MyInvocation.MyCommand.Path -Raw
$marker = "# === MAIN SCRIPT START ==="
$markerIndex = $scriptContent.IndexOf($marker)

if ($markerIndex -gt 0) {
$mainScript = $scriptContent.Substring($markerIndex + $marker.Length)
$fullScript = $scriptBlock + $mainScript

# Write temp script and execute in PS7 x64
$tempScript = "$env:TEMP\veeam-repo-ps7-$(Get-Random).ps1"
$fullScript | Out-File -FilePath $tempScript -Encoding UTF8 -Force

Write-Host "Relaunching in PowerShell 7 x64: $ps7Path"
& $ps7Path -NoProfile -ExecutionPolicy Bypass -File $tempScript
$exitCode = $LASTEXITCODE

Remove-Item $tempScript -Force -ErrorAction SilentlyContinue
exit $exitCode
}
else {
Write-Error "Could not find main script marker"
exit 1
}
}

# === MAIN SCRIPT START ===
# ===========================================
# Running in PS7 x64 - Execute Veeam commands
# ===========================================

# }
Write-Host "Running in PowerShell $($PSVersionTable.PSVersion) x64 - Proceeding with Veeam configuration"

Write-Host "The varialbes are now set."
Start-Transcript -Path $env:WINDIR\logs\veeam-add-backup-repo-ps7.log -Force

Write-Host "The variables are now set."
Write-Host "Repository type: $repositoryType"
Write-Host "Move backups: $moveBackups"
Write-Host "Description: $description"
Write-Host "Immutability period: $immutabilityPeriod"
Write-Host "Access key: $accessKey"
Write-Host "Secret key: **redacted for sensativity**"
Write-Host "Secret key: **redacted for sensitivity**"
Write-Host "S3 Endpoint: $endpoint"
Write-Host "S3 Region ID: $regionId"
Write-Host "S3 Bucket Name: $bucketName"
Write-Host "Automatic volume letter to create WinLocal repo: "
Write-Host "Drive letters to create WinLocal Repos: $driveLetters"


# Make sure PSModulePath includes Veeam Console
Write-Host "Installing Veeam PowerShell Module if not installed already."
$MyModulePath = "C:\Program Files\Veeam\Backup and Replication\Console\"
$env:PSModulePath = $env:PSModulePath + "$([System.IO.Path]::PathSeparator)$MyModulePath"
if ($Modules = Get-Module -ListAvailable -Name Veeam.Backup.PowerShell) {
try {
$Modules | Import-Module -WarningAction SilentlyContinue
}
catch {
throw "Failed to load Veeam Modules"
}
}

# Set Timestamp
# Write-Host "Getting timestamp for repository names."
# $timeStamp = [int](Get-Date -UFormat %s -Millisecond 0)
# REMOVED TO MAKE FOLDERNAME LOCATION GUID $folderName = $timeStamp

}
catch {
throw "Failed to load Veeam Modules"
}
}

if ($repositoryType -eq 1 -Or $repositoryType -eq 3) {
Write-Host "Creating S3 Repository: S3 $FolderName"
Write-Host "Creating S3 Repository: S3 $folderName"

# Add the S3 Account
$account = Add-VBRAmazonAccount -AccessKey $accessKey -SecretKey $secretKey -Description "$description $bucketName"
Expand All @@ -73,7 +143,7 @@ if ($repositoryType -eq 1 -Or $repositoryType -eq 3) {
Add-VBRAmazonS3CompatibleRepository `
-AmazonS3Folder $folder `
-Connection $connect `
-Name "S3 $FolderName" `
-Name "S3 $folderName" `
-EnableBackupImmutability `
-ImmutabilityPeriod $immutabilityPeriod `
-Description "$description $bucketName" `
Expand All @@ -82,7 +152,7 @@ if ($repositoryType -eq 1 -Or $repositoryType -eq 3) {
Add-VBRAmazonS3CompatibleRepository `
-AmazonS3Folder $folder `
-Connection $connect `
-Name "S3 $FolderName" `
-Name "S3 $folderName" `
-EnableBackupImmutability `
-ImmutabilityPeriod $immutabilityPeriod `
-Description "$description $bucketName"
Expand All @@ -92,37 +162,32 @@ if ($repositoryType -eq 1 -Or $repositoryType -eq 3) {
$repository
}


if ($repositoryType -eq 2 -Or $repositoryType -eq 3){
Write-Host "Creating local repository Local $FolderName"
Write-Host "Creating local repository Local $folderName"
# Get all logical drives on the system
$drives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }

# Find the drive with the largest total capacity
if ($drives.Count -gt 1){
$filteredDrives = $drives | Where-Object { $_.DeviceId -ne 'C:' }

} else {
$filteredDrives = $drives
}

# Create the local repository
$filteredDrives | ForEach-Object {
# $timeStamp = [int](Get-Date -UFormat %s -Millisecond 0)
$repositoryPath = Join-Path -Path $_.DeviceID -ChildPath "\veeam\$FolderName"
$repositoryName = "Local $FolderName"
$repositoryPath = Join-Path -Path $_.DeviceID -ChildPath "\veeam\$folderName"
$repositoryName = "Local $folderName"
Write-Host "Repository name: $repositoryName"
Write-Host "Repository path: $repositoryPath"
$repository = Add-VBRBackupRepository -Type WinLocal -Name "$repositoryName" -Folder $repositoryPath -Description "$description"
# Display the added repository details
# Display the added repository details
$repository
$localRepository = $repository
Start-Sleep -Seconds 1
}
}



# Move all local backups
if ($moveBackups -eq 1){
$backups = Get-VBRBackup | Where -Property TypeToString -ne "Backup Copy"
Expand All @@ -141,10 +206,6 @@ if ($moveListedBackups){
$moveListedBackups | ForEach-Object {
Move-VBRBackup -Repository $localRepository -Backup $_ -RunAsync
}

}

# Move local/scale out repo to new repo


Stop-Transcript