NEEDED: Script to back up Plex (for Windows)

Can anyone share a script (PowerShell, batch, or whatever else can run on a Windows system) to back up a Plex installation that could be used to recover from a hard drive crash or when moving the server to a new system? The script would need to:

  • Stop Plex server.
  • Copy Plex registry keys.
  • Copy the minimal application data files (metadata, media info, posters).
  • Remove files that are no longer needed from the previous backup.
  • Start Plex server.

Anyone? Thanks.

I know I’m a bit late to this post, but this is something I’ve started working on (again), I would have a completed script for you but I got lazy and didn’t finnish it, now that I am upgrading my server I’ve kicked myself into gear to get this done.

For me, the best approach is powershell, but you can use cmd or even vbscript, what you need to decide is how you want to deal with the backup.

You can back it up to a drive, secondary machine or store it online in a cloud service, you also have two options, either copy plex to your chosen location or compress it first then send it to your chosen location.

I’m going to give you some examples, as I said I’m not finnished creating my backup script and I’ll not just be backing up plex but sonarr, radarr and other applications, so while what I’m about to provide will work, how you implement it is entirely up to you, and remember, I’m using powershell.

First of all we have some variables, $Backup, $Plex_Dir, $Plex_Reg

$Backup = "Your_Backup_directory"

$Plex_Dir = "$env:LOCALAPPDATA\Plex Media Server"
$Plex_Reg = "HKCU\Software\Plex, Inc.\Plex Media Server"

Your backup directory can be anything, but if you’re going to do this over a network then ofcourse make sure you’re using a valid network path, in my case I’ll be transfering the backup to a google drive folder once its been compressed and my path will look something like this “c:\google_dirve”

So first things first, shutting down plex, if you run it as a service there is a cleaner way to shut it down, if not then we have to do it a dirtier way, the following will terminate plex.

taskkill /f /im "Plex Media Server.exe" /t
If you have it running as a service, then you’d run this command
net stop <service name>

Next, for me, I prefer to send my registry file for plex to the $Plex_Dir, that way, when I back it up, the registry is safe and sound in the plex folder.

The following code will export the $Plex_Reg to the $Plex_Dir

reg export $Plex_Reg  "$Plex_Dir\Plex.reg"

Next you need to decide how you want to handle the plex_dir and whether or not you wish to exclude certain folders.

If you’re going to compress it you need to decide what you’re going to compress it with, I chose 7za which is a standalone version of 7zip, this means I don’t need to install 7zip to do what I want, I also chose to split the backup into 1 Gigabyte files, this makes it easier to upload/download to/from my google drive.

My command for this is the following.

$7zexe = 'C:\Users\username\Desktop\7z\64\7za.exe'
$7zcmd = @(
    'a'
    '-t7z'
    "$Backup\plex.7z"
    $Plex_Dir
    '-mx0'
    '-xr!Plex Media Server\Diagnostics'
    '-xr!Plex Media Server\Scanners'
    '-xr!Plex Media Server\Crash Reports'
    '-xr!Plex Media Server\Updates'
    '-xr!Plex Media Server\Logs'
    '-v1g'
)

& $7zexe $7zcmd

-xr! is basically telling 7zip to exclude those folders, because I don’t need them in my backup and depending on the situation, I may not want to backup my cache folder either, if thats the case then I simply add this to the script above -xr!Plex Media Server\Cache, if you don’t want to split the file then remove -v1g from the code above.

If you’re looking to simply copy the plex folder you can use robocopy which is available in most windows OS, just open your cmd and type robocopy, if you don’t have it, then you will have to use something like xcopy.

Robocopy.exe "$Plex_Dir" "$Backup\Plex Media Server" /MIR /W:5 /R:2 /MT:24 /XD 'Logs' 'Crash Reports' 'Diagnostics' 'Scanners' 'Updates' 'Cache'

In the code above we’re excluding the same folders, except this time I’ve included the cache folder.

To start your server, if its a service you change the net stop to net start, or in powershell you run Start-Process "C:\Program Files (x86)\Plex\Plex Media Server\Plex Media Server.exe"

Something else, I would add to this, would be to delete the .reg file we exported to the plex directory, Remove-Item "$Plex_Dir\Plex.reg"

That’s pretty much it, powershell restricts custom scripts, so you will need to temporarly run –ExecutionPolicy Bypass when executing your script or you can remove the restriction so you don’t need to run that, the code would look something like this

powershell –ExecutionPolicy Bypass "& 'd:\script\backupplex.ps1'"

If you prefer to simply call the script without the bypass, then you’ll need to run Set-ExecutionPolicy RemoteSigned, use google to learn more before you decide to change your execution policy.

With that all said, depending on the method you chose, your script will look something like this.

$Backup = "Your_Backup_directory"

$Plex_Dir = "$env:LOCALAPPDATA\Plex Media Server"
$Plex_Reg = "HKCU\Software\Plex, Inc.\Plex Media Server"

$7zexe = 'C:\Users\username\Desktop\7z\64\7za.exe'

taskkill /f /im "Plex Media Server.exe" /t

reg export $Plex_Reg  "$Plex_Dir\Plex.reg"

$7zcmd = @(
    'a'
    '-t7z'
    "$Backup\plex.7z"
    $Plex_Dir
    '-mx0'
    '-xr!Plex Media Server\Diagnostics'
    '-xr!Plex Media Server\Scanners'
    '-xr!Plex Media Server\Crash Reports'
    '-xr!Plex Media Server\Updates'
    '-xr!Plex Media Server\Logs'
    '-v1g'
)

& $7zexe $7zcmd

Remove-Item "$Plex_Dir\Plex.reg"

Start-Process "C:\Program Files (x86)\Plex\Plex Media Server\Plex Media Server.exe"

OR

$Backup = "Your_Backup_directory"

$Plex_Dir = "$env:LOCALAPPDATA\Plex Media Server"
$Plex_Reg = "HKCU\Software\Plex, Inc.\Plex Media Server"

$7zexe = 'C:\Users\username\Desktop\7z\64\7za.exe'

net stop "Plex Media Server" <<or whatever your service name is

reg export $Plex_Reg  "$Plex_Dir\Plex.reg"

Robocopy.exe "$Plex_Dir" "$Backup\Plex Media Server" /MIR /W:5 /R:2 /MT:24 /XD 'Logs' 'Crash Reports' 'Diagnostics' 'Scanners' 'Updates' 'Cache'

Remove-Item "$Plex_Dir\Plex.reg"

net start "Plex Media Server" <<or whatever your service name is

You can also beef this up a bit by including some safety precautions, such as checking to see if the backup was succesful, testing the existence of the directory/registry paths etc…

Powershell is very useful and I would recommend you learn even the basics, I have a powershell script that automates sonarr, radarr and plex, it also extracts any files that need extracted, but its something I’m not willing to share just yet as I feel it can be improved upon :slight_smile:

You can download 7za from here , you’re looking for 7-Zip Extra: standalone console version, 7z DLL, Plugin for Far Manager

Once I’ve finnish my script and I’m happy with the results I will share it with the community, I think others have already done this, if you haven’t already consider searching the forums for other plex members scripts, try searching “powershell plex scripts” or something like that, anyway, I hope this helps, good luck.

3 Likes

Thank you. Any reason why you use 7zip instead of the built-in Compress-Archive (you can exclude folders using Compress-Archive)?

When I started automating my stystem, I used winrar because majority of the files I needed to extract were rar files, I later gravitated towards 7zip when I started to improve my automation script, 7zip still allows me to extract files like winrar except I only need 3 files (7za.exe, 7za.dll, 7zxa.dll) so my automation script is portable and when transfering or reinstalling my server it’s a simple drag and drop, I also think 7zip is/was better at extraction/compression than winrar and the built in windows feature.

But overall, I choose to use it because I’m already using it for my other script, if the built-in windows feature supported more file types for extracting and it worked almost as well as 7zip then I’d happily use it, but if I was in your shoes and didn’t need a third party file extractor then I’d just use the built-in feature to compress my backups.

1 Like

Btw, we’d need to implement the restore option to restore Plex from the backup, right? :slight_smile:

Yeah, that would be a seperate script, but would be simple, reg import path_to_file.reg and whatever you choose to extract it with, simply extract to path.

Here is my first draft:

# Location of the backup folder (the current backup will be created in a timestamped subfolder).
$BackupRootDir = "\\MYNAS\Backup\PlexBackup"

# Name of the backup file holding exported Plex registry key.
$BackupRegKeyFileName = "Plex.reg"

# Name of the ZIP file holding Plex application data files and folders.
$BackupZipFileName = "Plex.zip"

# Temp folder used to stage archiving job (use local drive for efficiency).
$TempZipFileDir = $env:TEMP

# Plex registry key path.
$PlexRegKey = "HKCU\Software\Plex, Inc.\Plex Media Server"

# Location of the Plex application data folder.
$PlexAppDataFolder = "$env:LOCALAPPDATA\Plex Media Server"

# The following application folders do not need to be backed up.
$ExcludeFolders = @("Diagnostics", "Scanners", "Crash Reports", "Updates", "Logs")

# Regular expression used to find names of the Plex Windows services.
$PlexServiceNameMatchString = "^Plex"

# Name of the Plex Media Server executable file.
$PlexServerExeFileName = "Plex Media Server.exe"

# Number of backups to retain: 0 - retain all, 1 - latest backup only, 2 - latest and one before it, etc.
$RetainBackups = 3

# Get list of all running Plex services.
$PlexServices = Get-Service | 
    Where-Object {$_.Name -match $PlexServiceNameMatchString} | 
		Where {$_.status –eq 'Running'}

# Stop all running Plex services.
if ($PlexServices.Count -gt 0)
{
    Write-Host "Stopping Plex service(s):"
    foreach ($PlexService in $PlexServices)
    {
        Write-Host " " $PlexService.Name
        Stop-Service -Name $PlexService.Name -Force
    }
}

# Get path of the Plex Media Server executable.
$PlexServerExePath = Get-Process | 
    Where-Object {$_.Path -match $PlexServerExeFileName + "$" } | 
		Select-Object -ExpandProperty Path

# Stop Plex Media Server executable (if it's still running).
if ($PlexServerExePath)
{
    Write-Host "Stopping Plex Media Server process:"
    Write-Host " " $PlexServerExeFileName
    taskkill /f /im $PlexServerExeFileName /t >$nul 2>&1
}

# Get current timestamp that will be used as a backup folder.
$BackupDir = (Get-Date).ToString("yyyyMMddHHmmss")

# Make sure that the backup parent folder exists.
New-Item -Path $BackupRootDir -ItemType Directory -Force | Out-Null

# Build backup folder path.
$BackupDirPath = Join-Path $BackupRootDir $BackupDir
Write-Host "New backup will be created under:"
Write-Host " " $BackupRootDir

# If the backup folder already exists, rename it by appending 1 (or next sequential number).
if (Test-Path -Path $BackupDirPath -PathType Container)
{
    $i = 1

    # Parse through all backups of backup folder (with appended counter).
    while (Test-Path -Path $BackupPath + "." + $i -PathType Container)
    {
        $i++
    }

    Write-Host "Renaming old backup folder to " + $BackupDir + "." + $i + "."
    Rename-Item -Path $BackupPath -NewName $BackupDir + "." + $i
}

# Delete old backup folders.
if ($RetainBackups -gt 0)
{
    # Get all folders with names matching our pattern "yyyyMMddHHmmss" from newest to oldest.
    $OldBackupDirs = Get-ChildItem -Path $BackupRootDir -Directory | 
        Where-Object { $_.Name -match '^\d{14}$' } | 
			Sort-Object -Descending

    $i = 1

    if ($OldBackupDirs.Count -ge $RetainBackups)
    {
        Write-Host "Purging old backup folder(s):"
    }

    foreach ($OldBackupDir in $OldBackupDirs)
    {
        if ($i -ge $RetainBackups)
        {
             Write-Host " " $OldBackupDir.Name
             Remove-Item $OldBackupDir.FullName -Force -Recurse -ErrorAction SilentlyContinue
        }

        $i++
    }
}

# Create new backup folder.
Write-Host "Creating new backup folder:"
Write-Host " " $BackupDir
New-Item -Path $BackupDirPath -ItemType Directory -Force | Out-Null

# Export Plex registry key.
Write-Host "Backing up registry key:"
Write-Host " " $PlexRegKey
Write-Host "to file:"
Write-Host " " $BackupRegKeyFileName
Write-Host "in:"
Write-Host " " $BackupDirPath
reg export $PlexRegKey (Join-Path $BackupDirPath $BackupRegKeyFileName) | Out-Null

# Compress Plex media folders.
$TempZipFileName= (New-Guid).Guid + ".zip"
$TempZipFilePath= Join-Path $TempZipFileDir $TempZipFileName

Write-Host "Copying Plex app data from:"
Write-Host " " $PlexAppDataFolder
Write-Host "to a temp file:"
Write-Host " " $TempZipFileName
Write-Host "in:"
Write-Host " " $TempZipFileDir
Get-ChildItem $PlexAppDataFolder -Directory  | 
    Where { $_.Name -notin $ExcludeFolders } | 
        Compress-Archive -DestinationPath $TempZipFilePath -Update

# Copy temp zip file to the backup folder.
Write-Host "Copying temp file:"
Write-Host " " $TempZipFileName
Write-Host "from:"
Write-Host " " $TempZipFileDir
Write-Host "to:"
Write-Host " " $BackupZipFileName
Write-Host "in:"
Write-Host " " $BackupDirPath
Start-BitsTransfer -Source $TempZipFilePath -Destination (Join-Path $BackupDirPath $BackupZipFileName)

# Delete temp file.
Write-Host "Deleting temp file:"
Write-Host " " $TempZipFileName
Write-Host "from:"
Write-Host " " $TempZipFileDir
Remove-Item $TempZipFilePath -Force

# Start all previously running Plex services (if any).
if ($PlexServices.Count -gt 0)
{
    Write-Host "Starting previously stopped Plex service(s):"
    foreach ($PlexService in $PlexServices)
    {
        Write-Host " " $PlexService.Name
    }
    $PlexServices | Start-Service
}

# Get name of the Plex Media Server executable (just to see if it is running).
$PlexServerExeName = Get-Process | 
    Where-Object {$_.Path -match $PlexServerExeFileName + "$" } | 
		Select-Object -ExpandProperty Name

# Start Plex Media Server executable (if it is not running).
if (!$PlexServerExeName -and $PlexServerExePath)
{
    Write-Host "Starting Plex Media Server process:"
    Write-Host " " $PlexServerExeFileName
    Start-Process $PlexServerExePath
}

Write-Host "Done."
1 Like

Very nice :slight_smile:

I’ve placed my current script at that bottom of this post.

I haven’t really decided on how many backups I should keep, I’m also indecisive on whether or not to make my sonarr and radarr backups daily/weekly, and my plex weekly/monthly or just run both weekly, sonarr doesn’t do anything on a sunday and radarr is random as it depends on whether or not something becomes available, But most of the content is Monday-Saturday so my backups will run on a Sunday morning, I’m also debating whether or not to include an alert via the discord or telegram app, something simple like…

# Succesfful
# Backup Successful for Sonarr
# Backup Successful for Radarr
# Backup Successful for Plex
# Types of failures
# Failed to authenticate Sonarr
# Failed to send backup request to Radarr
# Failed to compress plex : Error Reason here

I have an old webhook script I made a while back for discord, due to ombi crashing when I first set it up, turned out to be an issue with the application failing to run the service after it updated :confused:

I’m also in two minds on whether or not to use my vps to backup my files or google drive, at the moment I’m using gdrive by simply moving the backups to the CloudBackup folder which syncs to my gdrive, but at the same time I can create a script to upload all these files instead of having to install gdrive and if I do that then it might be better to just create a script that uploads to my vps but I’m not sure if I’ll be keeping that vps as its only being used to learn / play with linux.

Current Backup Script for plex, sonarr and radarr

I liked your idea to grab the fullname of the “plex media server.exe” via the Get-Process command so I incorporated that into my script, originally I had a set path since my servers path doesn’t really change, but i felt this would be handy for future upgrades :slight_smile:

With the holiday season keeping me busy, I probably won’t get a chance to improve / add more features during next week, but I look forward to seeing your final script and hopefully someone will be able to use our scripts to help them build their own or to simply use as a means to backup their plex and other applications.

Happy Holidays.

$BKUP_FILES_TO = "$PSScriptRoot\backup"
$MOVE_BKUP_TO = "$PSScriptRoot\CloudBackup"

$PLEX_EXEC_NAME = "Plex Media Server.exe"
$PLEX_DATA_FOLDER = "$env:LOCALAPPDATA\Plex Media Server"
$PLEX_DATA_REGISTRY = "HKCU\Software\Plex, Inc.\Plex Media Server"

# I wanted to keep the same name format as sonarr/radarr
$PLEX_BACKUP_NAME = "plex_backup_$(Get-Date -Format "yyyy.mm.dd_hh.mm.ss")" 

# "Barrowed" this from your script :)
$PLEX_EXEC_FULLNAME = Get-Process |
    Where-Object {$_.Path -match $PLEX_EXEC_NAME + "$" } |
        Select-Object -ExpandProperty Path

$7Z_EXEC = "$PSScriptRoot\7z\App\7-Zip64\7z.exe"

$7Z_PLEX = @(
    'a'
    '-t7z'
    "$BKUP_FILES_TO\$PLEX_BACKUP_NAME.7z"
    $PLEX_DATA_FOLDER
    '-mx0'
    '-xr!Plex Media Server\Diagnostics'
    '-xr!Plex Media Server\Scanners'
    '-xr!Plex Media Server\Crash Reports'
    '-xr!Plex Media Server\Updates'
    '-xr!Plex Media Server\Logs'
    '-xr!Plex Media Server\Cache'
    '-v1g'
)

# Sonarr / Radarr and soon to be Lidarr, this array is looped
# It Sends a backup request to sonarr/radarr,
# once complete it sends that backup_name.zip to the backup folder
$SODARR = @(
    @{
        "name" = "sonarr"
        "backup_folder" = "$env:programdata\NzbDrone\Backups\manual"

        "uri" = "http://localhost:8989/sonarr/api/command"
        "authKey" = "APIKEY"

        "data" = @{
            "name" = "backup"
            "type" = "manual"
        }
    }
    @{
        "name" = "radarr"
        "backup_folder" = "$env:programdata\Radarr\Backups\manual"

        "uri" = "http://localhost:7979/radarr/api/command"
        "authKey" = "APIKEY"

        "data" = @{
            "name" = "backup"
            "type" = "manual"
        }
    }
)

If(Test-Path($MOVE_BKUP_TO))
{
    If(!(Test-Path($BKUP_FILES_TO)))
    {
        New-Item -ItemType directory -Path $BKUP_FILES_TO 1>$null
    }

    $SODARR | % {
    
        $count = 0
        $FileExists = $false
        $TimeExceeded = $false

        $json = $_.data | ConvertTo-Json

        $uri = $_.uri+"/?apikey="+$_.authKey

        $name = $_.name

        Try
        {

            Invoke-RestMethod -Uri $uri -Method Post -Body $json 1>$null
            
            Do 
            {
                Get-ChildItem -Path $($_.backup_folder) -ErrorAction:SilentlyContinue | % {

                    $TimeDiff = $(get-date) - $($_.CreationTime)

                    If($TimeDiff.TotalMinutes -le 2) # If file was created within the last 2 minutes
                    {
                        Try
                        {
                            Move-Item -Path $($_.FullName) -Destination $MOVE_BKUP_TO -ErrorAction:Stop
                            $FileExists = $true
                        } 
                        Catch
                        {
                            If($_.exception -like "*USE BY ANOTHER PROCESS*" -and $count -eq 0)
                            {
                                "$name appears to be processing the backup"
                            }
                            ElseIf($_.exception -like "*USE BY ANOTHER PROCESS*" -and $count -gt 149)
                            {
                                "$name is still processing the backup"
                            }
                        }
                        Finally
                        {
                            $error.Clear()
                        }
                    }
                }
                If($count -gt 150) # 150 loops * 2 seconds = 300 seconds
                {
                    $TimeExceeded = $True
                    Write-Output "Skipping process, time limit exceeded"
                }
                Else
                {
                    $count++
                    Start-Sleep -Seconds 2
                }
            }
            Until($FileExists -or $TimeExceeded) # 300 seconds / 60 seconds = 5 min time limit
        }
        Catch
        {
            If($_.exception -like "*(401) UNAUTHORIZED*" -and $count -eq 0)
            {
                "Unauthorized error occured for $name, please check authkey"
            }
            Else
            {
                $_.exception
            }
        }
        Finally
        {
            $error.Clear()
        }
    }

    If(Test-Path($PLEX_DATA_FOLDER)) #Make sure our plex data folder exists before we continue
    {
        Get-Service -Name "plex*" | % { #Find any running service belonging to plex
            If($_.Status -ieq "running")
            {
                Stop-Service -Name $_.Name #Stop services
            }
        }

        If($(Get-Process -Name "Plex*").Count -gt 0)
        {
            taskkill /f /im $PLEX_EXEC_NAME /t 1>$null
        }

        #I decide to keep the Plex.reg file in this folder and simply overwrite it each time (using "/y")
        reg export $PLEX_DATA_REGISTRY "$PLEX_DATA_FOLDER\Plex.reg" /y 1>$null 

        Write-Output "Attempting to compress into 1 Gigabyte archives"

        Try
        {          
            & $7Z_EXEC $7Z_PLEX

            Write-Output "Files compressed to $BKUP_FILES_TO\$PLEX_BACKUP_NAME.7z"

        }
        Catch
        {
            Write-Output "Compression failed, $($_.exception)"
        }

    }

    Write-Output "Starting Plex Server"

    Start-Process $PLEX_EXEC_FULLNAME

    Get-Service -Name "plex*" | % { #Find any stopped service belonging to plex
        If($_.Status -ieq "stopped")
        {
            Start-Service -Name $_.Name
        }
    }

    Move-Item -Path $BKUP_FILES_TO\*.7z* -Destination $MOVE_BKUP_TO
}
2 Likes

Updated script to do backups and restores:

#Requires -Version 4.0
#Requires -RunAsAdministrator

#------------------------------[ HELP INFO ]-------------------------------

<#
.SYNOPSIS
Backs up or restores Plex application data files and registry key(s) on a Windows system.

.DESCRIPTION
This script must be run as an administrator.

The execution policy must allow running scripts. 

To check the execution policy, run the following command:

  Get-ExecutionPolicy

If the execution policy doe snot allow running scripts, do the following:

(1) Start Windows PowerShell with the "Run as Administrator" option. 
(2) Run the following command: Set-ExecutionPolicy RemoteSigned

This will allow running unsigned scripts that you write on your local computer and signed scripts from Internet.

See also 'Running Scripts' at Microsoft TechNet Library:
https://docs.microsoft.com/en-us/previous-versions//bb613481(v=vs.85)

.PARAMETER Restore
Indicates that the script should restore Plex application data from a backup.

.PARAMETER BackupDirPath
When running this script in the Restore mode, specifies path to the backup folder.

.PARAMETER BackupRootDir
Path to the root backup folder holding timestamped backup subfolders.

.PARAMETER PlexAppDataDir
Location of the Plex application data folder.

.NOTES
Version    : 1.0
Author     : Alek Davis
Created on : 2019-01-23

.LINK
https://forums.plex.tv/t/needed-script-to-back-up-plex-for-windows/329014

.INPUTS
None.

.OUTPUTS
None.

.EXAMPLE
PlexBackup.ps1
Backs up Plex application data to the default backup location.

.EXAMPLE
PlexBackup.ps1 -BackupRootDir "\\MYNAS\Backup\Plex"
Backs up Plex application data to the specified backup location on a network share.

.EXAMPLE
PlexBackup.ps1 -Restore
Restores Plex application data from the latest backup in the default folder.

.EXAMPLE
PlexBackup.ps1 -Restore -BackupDirPath "\\MyNas\PlexBackup\20190101183015"
Restores Plex application data from a backup in the specified remote folder.

.EXAMPLE
Get-Help .\PlexBackup.ps1
View help information.
#>

#------------------------[ COMMAND-LINE SWITCHES ]-------------------------

# Script command-line arguments (see descriptions in the .PARAMETER comments above).
param
(
    [switch]$Restore = $false,
    [string]$BackupDirPath = $null,
    [string]$BackupRootDir = "D:\PlexBackup",
    [string]$PlexAppDataDir = "$env:LOCALAPPDATA\Plex Media Server"
)

#-----------------------------[ DECLARATIONS ]-----------------------------

# Stop script script execution on non-fatal errors.
# $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

# Name of the backup file holding exported Plex registry key.
$BackupRegKeyFileName = "Plex.reg"

# Name of the ZIP file holding Plex application data files and folders.
$BackupZipFileName = "Plex.zip"

# Extension of the archive file.
$ZipFileExt = ".zip"

# Temp folder used to stage archiving job (use local drive for efficiency).
$TempZipFileDir = $env:TEMP

# To bypass the staging archive folder, set $TempZipFileDir to $null.
# $TempZipFileDir = $null

# Plex registry key path.
$PlexRegKey = "HKCU\Software\Plex, Inc.\Plex Media Server"

# The following Plex application folders do not need to be backed up.
$ExcludePlexAppDataDirs = @("Diagnostics", "Scanners", "Crash Reports", "Updates", "Logs")

# Regular expression used to find display names of the Plex Windows service(s).
$PlexServiceNameMatchString = "^Plex"

# Name of the Plex Media Server executable file.
$PlexServerExeFileName = "Plex Media Server.exe"

# If Plex Media Server is not running, define path to the executable here.
$PlexServerExePath = $null

# Number of backups to retain: 
# 0 - retain all previously created backups,
# 1 - latest backup only,
# 2 - latest and one before it,
# 3 - latest and two before it, 
# and so on.
$RetainBackups = 3

#------------------------------[ FUNCTIONS ]-------------------------------

function AbortScript
{
    param
    (
        [int]$errorCode=1
    )
    
    $Host.SetShouldExit($errorCode)
    exit $errorCode
}

function GetPlexMediaServerExePath
{
    param
    (
        [string]$path,
        [string]$name
    )

    # Get path of the Plex Media Server executable.
    if (!$path)
    {
        $path = Get-Process | 
            Where-Object {$_.Path -match $name + "$" } | 
                Select-Object -ExpandProperty Path
    }

    # Make sure we got the Plex Media Server executable file path.
    if (!$path)
    {
        Write-Error "Cannot determine path of the the Plex Media Server executable file."
        return $false, $null
    }

    # Verify that the Plex Media Server executable file exists.
    if (!(Test-Path -Path $path -PathType Leaf))
    {
        Write-Error "Plex Media Server executable file at:"
        Write-Error " " $path
        Write-Error "does not exist."
        return $false, $null
    }

    return $true, $path
}

function GetRunningPlexServices
{
    param
    (
        [string]$displayNameSearchString
    )

    return Get-Service | 
        Where-Object {$_.DisplayName -match $displayNameSearchString} | 
            Where-Object {$_.status -eq 'Running'}
}

function StopPlexServices
{
    param
    (
        [object[]]$services
    )

    # We'll keep track of every Plex service we successfully stopped.
    $stoppedPlexServices = [System.Collections.ArrayList]@()

    if ($services.Count -gt 0)
    {
        Write-Host "Stopping Plex service(s):"
        foreach ($service in $services)
        {
            Write-Host " " $service.DisplayName
            Stop-Service -Name $service.Name -Force

            if ($Error.Count > 0)
            {
                return $false, $null
            }

            $i = $stoppedPlexServices.Add($service)
        }
    }

    return $true, $stoppedPlexServices
}

function StartPlexServices
{
    param
    (
        [object[]]$services
    )

    if (!$services)
    {
        return
    }

    if ($services.Count -le 0)
    {
        return
    }

    Write-Host "Starting Plex service(s):"

    foreach ($service in $services)
    {
        Write-Host " " $service.DisplayName
        Start-Service -Name $service.Name
    }
}

function StopPlexMediaServer
{
    param
    (
        [string]$plexServerExeFileName,
        [string]$plexServerExePath
    )
    
    $Error.Clear()

    # If we have the path to Plex Media Server executable, see if it's running.
    $exeFileName = $plexServerExeFileName

    if ($plexServerExePath)
    {
        $exeFileName = Get-Process | 
            Where-Object {$_.Path -eq $plexServerExePath } | 
		        Select-Object -ExpandProperty Name
    }
    # If we do not have the path, use the file name to see if the process is running.
    else
    {
        $plexServerExePath = Get-Process | 
            Where-Object {$_.Path -match $plexServerExeFileName + "$" } | 
                Select-Object -ExpandProperty Path
    }

    # Stop Plex Media Server executable (if it is running).
    if ($exeFileName -and $plexServerExePath)
    {
        Write-Host "Stopping Plex Media Server process:"
        Write-Host " " $plexServerExeFileName
        taskkill /f /im $plexServerExeFileName /t >$nul 2>&1
    }

    if ($Error.Count -gt 0)
    {
        return $false
    }

    return $true
}

function StartPlexMediaServer
{
    param
    (
        [string]$plexServerExeFileName,
        [string]$plexServerExePath
    )
    
    $Error.Clear()

    # Get name of the Plex Media Server executable (just to see if it is running).
    $plexServerExeFileName = Get-Process | 
        Where-Object {$_.Path -match $plexServerExeFileName + "$" } | 
            Select-Object -ExpandProperty Name

    # Start Plex Media Server executable (if it is not running).
    if (!$plexServerExeFileName -and $plexServerExePath)
    {
        Write-Host "Starting Plex Media Server process:"
        Write-Host " " $plexServerExePath
        Start-Process $plexServerExePath
    }
}

function RestorePlexFromBackup
{
    param
    (
        [string]$plexAppDataFolder,
        [string]$plexRegKey,
        [string]$backupDirPath,
        [string]$backupRootDir,
        [string]$backupZipFilePath,
        [string]$backupZipFileName,
        [string]$backupRegKeyFileName,
        [string]$tempZipFileDir,
        [string]$zipFileExt
    )
    $Error.Clear()

    Write-Host "Restoring Plex application data from a backup."

    # If the backup folder is not specified, use the latest timestamped
    # folder under the backup directory defined at the top of the script.
    if (!$backupDirPath)
    {
        # Get all folders with names matching our pattern "yyyyMMddHHmmss" 
        # from newest to oldest.
        $oldBackupDirs = Get-ChildItem -Path $backupRootDir -Directory | 
            Where-Object { $_.Name -match '^\d{14}$' } | 
                Sort-Object -Descending

        if ($oldBackupDirs.Count -lt 1)
        {
            Write-Error "No backup folder matching timestamp format 'yyyyMMddHHmmss' found under:"
            Write-Error " " $backupRootDir

            return $false
        }
        else
        {
            # Use the first subfolder with the latest timestamp.
            $backupDirPath = $oldBackupDirs[0].FullName

            Write-Host "Backup folder path:"
            Write-Host " " $backupDirPath
        }
    }

    # Make sure the backup folder exists.
    if (!(Test-Path $backupDirPath -PathType Container))
    {
        Write-Error "Backup folder:"
        Write-Error " " $backupDirPath
        Write-Error "does not exist."
        
        return $false
    }

    # Build path to the archive file.
    $backupZipFilePath = Join-Path $backupDirPath $backupZipFileName 

    # Make sure the backup zip file exists.
    if (!(Test-Path $backupZipFilePath -PathType Leaf))
    {
        Write-Error "Backup archive file:"
        Write-Error " " $backupZipFileName
        Write-Error "does not exist in:"
        Write-Error " " $backupDirPath

        return $false
    }

    $tempZipFileName= (New-Guid).Guid + $zipFileExt

    $Error.Clear()

    # If we have a temp folder, stage the extracting job.
    if ($tempZipFileDir)
    {
        $tempZipFilePath= Join-Path $tempZipFileDir $tempZipFileName
    
        # Copy backup archive to a temp zip file.
        Write-Host "Copying backup archive file:"
        Write-Host " " $backupZipFileName
        Write-Host "from:"
        Write-Host " " $backupDirPath
        Write-Host "to a temp zip file:"
        Write-Host " " $tempZipFileName
        Write-Host "in:"
        Write-Host " " $tempZipFileDir
        Start-BitsTransfer -Source $backupZipFilePath -Destination $tempZipFilePath

        if ($Error.Count -gt 0)
        {
            return $false
        }

        Write-Host "Restoring Plex app data from:"
        Write-Host " " $tempZipFileName
        Write-Host "in:"
        Write-Host " " $tempZipFileDir
        Write-Host "to Plex app data folder in:"
        Write-Host " " $plexAppDataFolder
        Expand-Archive -Path $tempZipFilePath -DestinationPath $plexAppDataFolder -Force

        if ($Error.Count -gt 0)
        {
            return $false
        }
         
        # Delete temp file.
        Write-Host "Deleting temp file:"
        Write-Host " " $tempZipFileName
        Write-Host "from:"
        Write-Host " " $tempZipFileDir
        Remove-Item $tempZipFilePath -Force
    }
    # If we don't have a temp folder, restore archive in the Plex application data folder.
    else 
    {
        Write-Host "Restoring Plex app data from:"
        Write-Host " " $backupZipFileName
        Write-Host "from:"
        Write-Host " " $backupDirPath
        Write-Host "to Plex app data folder in:"
        Write-Host " " $plexAppDataFolder
        Expand-Archive -Path (Join-Path $backupDirPath $backupZipFileName) `
            -DestinationPath $plexAppDataFolder -Force

        if ($Error.Count -gt 0)
        {
            return $false
        }
    }

    # Import Plex registry key.
    Write-Host "Restoring registry key from file:"
    Write-Host " " $backupRegKeyFileName
    Write-Host "in:"
    Write-Host " " $backupDirPath

    Invoke-Command {reg import (Join-Path $backupDirPath $backupRegKeyFileName) *>&1 | Out-Null}

    return $true
}

function CreatePlexBackup
{
    param
    (
        [string]$plexAppDataDir,
        [string]$plexRegKey,
        [string]$backupRootDir,
        [string]$backupZipFilePath,
        [string]$backupZipFileName,
        [string]$backupRegKeyFileName,
        [string]$tempZipFileDir,
        [string[]]$excludePlexAppDataDirs,
        [int]$retainBackups,
        [string]$zipFileExt
    )
    $Error.Clear()

    Write-Host "Backing up Plex application data."

    # Get current timestamp that will be used as a backup folder.
    $backupDir = (Get-Date).ToString("yyyyMMddHHmmss")

    # Make sure that the backup parent folder exists.
    New-Item -Path $backupRootDir -ItemType Directory -Force | Out-Null

    # Verify that we got the backup parent folder.
    if (!(Test-Path $backupRootDir -PathType Container))
    {
        Write-Error "Backup folder does not exist and cannot be created:"
        Write-Error " " $backupRootDir
        return $false
    }

    $Error.Clear()

    # Build backup folder path.
    $backupDirPath = Join-Path $backupRootDir $backupDir
    Write-Host "New backup will be created under:"
    Write-Host " " $backupRootDir

    # If the backup folder already exists, rename it by appending 1 
    # (or next sequential number).
    if (Test-Path -Path $backupDirPath -PathType Container)
    {
        $i = 1

        # Parse through all backups of backup folder (with appended counter).
        while (Test-Path -Path $backupDirPath + "." + $i -PathType Container)
        {
            $i++
        }

        Write-Host "Renaming old backup folder to " + $backupDir + "." + $i + "."
        Rename-Item -Path $backupDirPath -NewName $backupDir + "." + $i

        if ($Error.Count -gt 0)
        {
            return $false
        }
    }

    # Delete old backup folders.
    if ($retainBackups -gt 0)
    {
        # Get all folders with names matching our pattern "yyyyMMddHHmmss" 
        # from newest to oldest.
        $oldBackupDirs = Get-ChildItem -Path $backupRootDir -Directory | 
            Where-Object { $_.Name -match '^\d{14}$' } | 
                Sort-Object -Descending

        $i = 1

        if ($oldBackupDirs.Count -ge $retainBackups)
        {
            Write-Host "Purging old backup folder(s):"
        }

        foreach ($oldBackupDir in $oldBackupDirs)
        {
            if ($i -ge $retainBackups)
            {
                 Write-Host " " $oldBackupDir.Name
                 Remove-Item $oldBackupDir.FullName -Force -Recurse `
                    -ErrorAction SilentlyContinue         
            }

            $i++
        }
    }

    $Error.Clear()

    # Create new backup folder.
    Write-Host "Creating new backup folder:"
    Write-Host " " $backupDir
    New-Item -Path $backupDirPath -ItemType Directory -Force | Out-Null

    # Compress Plex media folders.
    $tempZipFileName= (New-Guid).Guid + $zipFileExt

    $Error.Clear()

    # If we have a temp folder, stage the archiving job.
    if ($tempZipFileDir)
    {
        # Make sure temp folder exists.
        if (!(Test-Path -Path $tempZipFileDir -PathType Container))
        {
            Write-Error "Temp staging folder:"
            Write-Error " " $tempZipFileDir
            Write-Error "does not exist."

            return $false
        }

        $tempZipFilePath= Join-Path $tempZipFileDir $tempZipFileName

        Write-Host "Copying Plex app data from:"
        Write-Host " " $plexAppDataDir
        Write-Host "to a temp file:"
        Write-Host " " $tempZipFileName
        Write-Host "in:"
        Write-Host " " $tempZipFileDir
        Get-ChildItem $plexAppDataDir -Directory  | 
            Where-Object { $_.Name -notin $excludePlexAppDataDirs } | 
                Compress-Archive -DestinationPath $tempZipFilePath -Update

        if ($Error.Count -gt 0)
        {
            return $false
        }

        # Copy temp zip file to the backup folder.
        Write-Host "Copying temp file:"
        Write-Host " " $tempZipFileName
        Write-Host "from:"
        Write-Host " " $tempZipFileDir
        Write-Host "to:"
        Write-Host " " $backupZipFileName
        Write-Host "in:"
        Write-Host " " $backupDirPath
        Start-BitsTransfer -Source $tempZipFilePath `
            -Destination (Join-Path $backupDirPath $backupZipFileName)        
    
        if ($Error.Count -gt 0)
        {
            return $false
        }

        # Delete temp file.
        Write-Host "Deleting temp file:"
        Write-Host " " $tempZipFileName
        Write-Host "from:"
        Write-Host " " $tempZipFileDir
        Remove-Item $tempZipFilePath -Force
    }
    # If we don't have a temp folder, create an archive in the destination folder.
    else 
    {
        $zipFilePath= Join-Path $backupDirPath $backupZipFileName

        Write-Host "Copying Plex app data from:"
        Write-Host " " $plexAppDataDir
        Write-Host "to a file:"
        Write-Host " " $backupZipFileName
        Write-Host "in:"
        Write-Host " " $backupDirPath
        Get-ChildItem $plexAppDataDir -Directory  | 
            Where-Object { $_.Name -notin $excludePlexAppDataDirs } | 
                Compress-Archive -DestinationPath $zipFilePath -Update

        if ($Error.Count -gt 0)
        {
            retrun $false
        }
    }

    # Export Plex registry key.
    Write-Host "Backing up registry key:"
    Write-Host " " $plexRegKey
    Write-Host "to file:"
    Write-Host " " $backupRegKeyFileName
    Write-Host "in:"
    Write-Host " " $backupDirPath
    Invoke-Command {reg export $plexRegKey (Join-Path $backupDirPath $backupRegKeyFileName) *>&1 | Out-Null}

    return $true
}

#---------------------------------[ MAIN ]---------------------------------

# Clear screen.
Clear-Host

# Log the mode.
if ($Restore)
{
    Write-Host "Running RESTORE script."
}
else
{
    Write-Host "Running BACKUP script."
}

# Determine path to Plex Media Server executable.
$Success, $PlexServerExePath = 
    GetPlexMediaServerExePath $PlexServerExePath $PlexServerExeFileName

if (!$Success)
{
    AbortScript
}

# Get list of all running Plex services (match by display name).
$PlexServices = GetRunningPlexServices $PlexServiceNameMatchString

# Stop all running Plex services.
$Success, $PlexServices = StopPlexServices $PlexServices
if (!$Success)
{
    StartPlexServices $PlexServices
    AbortScript
}

# Stop Plex Media Server executable (if it's still running).
if (!(StopPlexMediaServer $PlexServerExeFileName $PlexServerExePath))
{
    StartPlexServices $PlexServices
    AbortScript
}

# Restore Plex application data from backup.
if ($Restore)
{
    $Success = RestorePlexFromBackup `
        $PlexAppDataDir `
        $PlexRegKey `
        $BackupDirPath `
        $BackupRootDir `
        $BackupZipFilePath `
        $BackupZipFileName `
        $BackupRegKeyFileName `
        $TempZipFileDir `
        $ZipFileExt
}
# Back up Plex application data.
else
{
    $Success = CreatePlexBackup `
        $PlexAppDataDir `
        $PlexRegKey `
        $BackupRootDir `
        $BackupZipFilePath `
        $BackupZipFileName `
        $BackupRegKeyFileName `
        $TempZipFileDir `
        $ExcludePlexAppDataDirs `
        $RetainBackups `
        $ZipFileExt
}

# Start all previously running Plex services (if any).
StartPlexServices $PlexServices

# Start Plex Media Server (if it's not running).
StartPlexMediaServer $PlexServerExeFileName $PlexServerExePath

if (!$Success)
{
    Write-Error "Failed."
    AbortScript
}
else
{
    Write-Host "Done."
}

Made a couple of changes and added to GitHub.

1 Like

Added a few improvements (allow resuming a failed backup, support config file). Check it out: https://github.com/alekdavis/PlexBackup

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.