[Windows] Introduction to PowerShell Post-Scripting!

Come up with a useful post-processing script? Share it here!
Post Reply
Owens
Newbie
Newbie
Posts: 4
Joined: April 1st, 2015, 2:37 am

[Windows] Introduction to PowerShell Post-Scripting!

Post by Owens »

Hey guys,

I've Been lurking around here for a while in search for some code snippets to build a custom script for post-processing.
I've noticed these forums are a bit lackluster for anything related to PowerShell scripting.
A few months back I started delving deep into PS at work to automate a few of my admin tasks and haven't looked back since. It was one of those things I knew existed, but never really paid attention to it for years.

Today I am going to give a small introduction into the basic PowerShell scripting and how you can incorporate them into Post-Processing for SABnzb.
Despite the name, PowerShell is a bloody powerful and efficient tool with little coding needed to get the output results you desire.
Over time I will also post up more scripts as I develop them to hopefully give you a better insight and give you some inspiration to write your own ;)
This post is aimed at individuals who are already familiar with cmd/.bat scripting, who can easily adopt their skills to PS and reap the benefits.

Prerequisites:
Before we start, I recommend upgrading PowerShell to 4.0 if possible as its a long way since the early days of 2.0:
  • If you are running Windows 8.1 or Server 2012 R2, then PowerShell 4.0 comes pre-installed.
  • If you are running Windows 8 or Server 2012, then PowerShell 3.0 comes pre-installed.
    Note: You cannot install PowerShell 4.0 on Windows 8, but you can on Server 2012. Go figure.
    You should be able to get away with 3.0 for your basic scripting needs so stick to that.
  • If you are running Windows 7 or Server 2008 R2, then PowerShell 2.0 comes pre-installed.
    You can upgrade to 4.0 using the following packages:
    1. Windows 7 Service Pack 1 - You likely already have this, but check the properties in System.
    2. Microsoft .NET Framework 4.5 - The core Framework needed for PowerShell 4.0
    3. Windows Management Framework 4.0 - The main Framework which includes PowerShell 4.0 and PowerShell ISE
Getting started - Bootstrap:
You are probably familiar with hooking .exe or .bat files in SABnzb for processing your completed jobs.
We can use a Bootstrap to fire up our PS scripts and parsing the same variables from SABnzb onto the PS script as you would with any other scripting language.

The PSBootStrap.bat:

Code: Select all

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -File "D:\Scripts\ProcessMovies.ps1" -JobName "%3" -CompleteJobDir "%1"
So what's going on here?
We are calling the powershell.exe shell and specifying our .ps1 script to execute with -File parameter.
As described by the User Script Wiki Article, We can parse variables from our job onto our ProcessMovies.ps1 script. In this example we are passing -JobName "%3" and -CompleteJobDir "%1"
So we are on the same page, our job example will be -JobName "My.Home.Wedding.2015.720p.BluRay.DTS.x264-NoGRP" and -CompleteJobDir "D:\Complete\My.Home.Wedding.2015.720p.BluRay.DTS.x264-NoGRP"

PowerShell comes in 2 flavours - 32Bit and 64Bit which we are using the latter. However if you are using 32-Bit Windows you can change the path accordingly:
32-Bit: C:\WINDOWS\syswow64\Windowspowershell\v1.0\powershell.exe
64-Bit: C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
No, that's not a typo. Mind fucked right? ??? As was I. After 15 minutes of scratching around, several sources have confirmed this. Also disregard \v1.0\ even though you are running PS 4.0.

Anyways, moving on!

Script Editing - PowerShell ISE:
One of the cool hidden gems in windows is PowerShell ISE (Integrated Scripting Environment). You can find it by typing in "ISE" into your start search. I use this on almost a daily basis at work and it provides a neat environment for real-time testing and debugging.
Here are also 10 reasons for using PowerShell ISE instead of the PowerShell console. But it wont take long to convince you.
Pro tip: As I tend to stare at my screen hours on end, it's a good idea to change the theme to something that' easier on your eyes once you get your hands more dirty down the track.
In ISE, click on Tools -> Options... -> Manage Themes and select "Dark Console, Dark Editor". You will thank me later.

Script Example - A Breakdown:
I will break my sample script down into several parts to explain best I can, then provide the full script at the end.

This is something I wrote for myself that does the following:
1. Finds movies files of a particular type and size.
2. Rename the movie file based on the job name.
3. Moves the movie file to my collection.
4. Sends recipients a email notification to say the job is complete.

Let's Start:

Code: Select all

param(
      [string]$JobName,
      [string]$CompleteJobDir
)
Here we are specifying the parameters so we can grab the completed job variables from SABnzb and pass them onto our script. These relate to the -JobName and -CompleteJobDir in the PSBootstrap.bat I mentioned earlier.
You can have as many parameters as you like to pass on variables. As a rule of thumb, I always specify parameters the first lines of my script.

Code: Select all

$FileSizeMB = 2000
$FileSizeB = ($FileSizeMB * 1024 * 1024)
[String]$FileLocation = Get-ChildItem -Path $CompleteJobDir –Recurse -Include *.mkv | Where-Object {$_.length -gt $FileSizeB}
Here we are obtaining the file location of our movie file based on file extension *.mkv and a size greater than 2GB.
I know this isn't an absolute fool-proof way of finding the movie file, but I chose this because:
A) I am very particular about what movie format/containers I download. All of mine have .mkv extensions and is the most popular.
B) Most if not all of my movies are larger than 2GB. This should rule out any sample.mkv files which are useless to me.
You probably also noticed $FileSizeMB = 2000. Since the Get-ChildItem cmdlet handles files in Bytes size, I started off with MegaBytes because its an easier size to specify. You can then convert MegaBytes to Bytes using ($FileSizeMB * 1024 * 1024) to pass onto the cmdlet.

Code: Select all

If ($JobName -contains ".~DG~") {$JobName = $JobName.TrimEnd(".~DG~")}
ElseIf ($JobName -contains ".DG") {$JobName = $JobName.TrimEnd(".DG")}
ElseIf ($JobName -contains "~DG~") {$JobName = $JobName.TrimEnd("~DG~")}
This isn't really necessary, though one of the indexers I use like to put "DG" onto the end of some of their bins.
I find this rather annoying, so we can clean up the job name by trimming off "DG" off the end of the job name using every combination I've picked up in the past. This article gives a bit more insight into Trimming.

Code: Select all

Rename-Item $FileLocation "$($JobName).mkv"
[String]$FileLocation = Get-ChildItem -Path $CompleteJobDir –Recurse -Include *.mkv | Where-Object {$_.length -gt $FileSize}
Here we are renaming our movie file based on the job name as I like to keep my collection uniform.
Sometimes when downloading bins you will notice the movie file has a very whacky filename like 5e04363a22109a1.mkv
In our example the Get-ChildItem cmdlet picked up our movie path as "D:\Complete\My.Home.Wedding.2015.720p.BluRay.DTS.x264-NoGRP\5e04363a22109a1.mkv"
We use Rename-Item cmdlet to change the file name to something more meaningful like "My.Home.Wedding.2015.720p.BluRay.DTS.x264-NoGRP.mkv"
Pro tip: We can resolve variable values on the fly by encapsulating them inside $(). "$($JobName).mkv" becomes "My.Home.Wedding.2015.720p.BluRay.DTS.x264-NoGRP.mkv"
Last but not least, we re-obtain the path to the movie file using Get-ChildItem cmdlet again since the file has been renamed.

Code: Select all

Move-Item $FileLocation "F:\HD MOVIES" -Force
Remove-Item -Path "D:\Complete\$($JobName)" -Force -Recurse -ErrorAction SilentlyContinue
Here we move the movie file to my final collection for storage using the Move-Item cmdlet
We then clean up our Complete directory by removing the original completed job directory using Remove-Item cmdlet.
Pretty straight forward eh?

Code: Select all

[array]$SMTPTo = "Joe Bloggs <[email protected]>", "Anthony Smith <[email protected]>"
$SMTPFrom = "[email protected]"
$SMTPServer = "smtp.server.com"
$SMTPPriority = "Normal"
$SMTPSubject = "Downloaded: $($JobName)"
$SMTPBody = "Movie available in: \\server\HD MOVIES\$($JobName).mkv"
Send-MailMessage -To $SMTPTo -From $SMTPFrom -SMTPServer $SMTPServer -Subject $SMTPSubject -Body $SMTPBody -Priority $SMTPPriority
Finally, we can send an email notification to ourselves using the Send-MailMessage cmdlet. The code above is pretty straight forward.
You can send a notification to multiple people by splitting out the recipients into an array as we have with [array]$SMTPTo.

That's it for now!

The full ProcessMovies.ps1 script:

Code: Select all

param(
  [string]$JobName,
  [string]$CompleteJobDir
)

# Specify files greater than XX size below in MB

$FileSizeMB = 2000

# Convert Megabytes to Bytes

$FileSizeB = ($FileSizeMB * 1024 * 1024)

# Get location of video file in completed directory. Scan sub directories as well if its hidden deep inside.

Write-Host "Obtaining location of video file.."

[String]$FileLocation = Get-ChildItem -Path $CompleteJobDir –Recurse -Include *.mkv | Where-Object {$_.length -gt $FileSizeB}

Write-Host "Found: $($FileLocation)"

# Clean up job name if it contains unwanted text.
# One silly NZB Scraper I use puts DG on the end of releases that are dupes in different Usenet groups.

If ($JobName -contains ".~DG~") $JobName = $JobName.TrimEnd(".~DG~")}
ElseIf ($JobName -contains ".DG") {$JobName = $JobName.TrimEnd(".DG")}
ElseIf ($JobName -contains "~DG~") {$JobName = $JobName.TrimEnd("~DG~")}

# Rename video file to job name

Write-Host "Renaming movie file to $($JobName).mkv"

Rename-Item $FileLocation "$($JobName).mkv"

# Get location of renamed video file again

Write-Host "Re-obtaining location of video file..."

[String]$FileLocation = Get-ChildItem -Path $CompleteJobDir –Recurse -Include *.mkv | Where-Object {$_.length -gt $FileSizeB}

Write-Host "Found: $($FileLocation)"

# Move video file to final storage location

Write-Host "Moving movie file to F:\HD MOVIES\$($JobName).mkv"

Move-Item $FileLocation "F:\HD MOVIES" -force

# Pre-defined parameters for Email Notifications

[array]$SMTPTo = "Joe Bloggs <[email protected]>", "Anthony Smith <[email protected]>"
$SMTPFrom = "[email protected]"
$SMTPServer = "smtp.server.com"
$SMTPPriority = "Normal"
$SMTPSubject = "Downloaded: $($JobName)"
$SMTPBody = "Movie available in: \\server\HD MOVIES\$($JobName).mkv"

# Send email notification saying job is complete.

Write-Host "Emailing notifications.."

Send-MailMessage -To $SMTPTo -From $SMTPFrom -SMTPServer $SMTPServer -Subject $SMTPSubject -Body $SMTPBody -Priority $SMTPPriority

Write-Host "All done! Exit code: 0"
Conclusion:
  • In this example we made a simple process of renaming a movie file and moving it to a storage collection, and sending out a notification once the job is complete.
  • I'm aware this example doesn't include .srt or .sub files. With a little creativity you can also process your sub files along with the main movie file.
  • I know SABnzb can send out email notifications when jobs are complete, but they can get a bit annoying if you are downloading entire TV seasons with each episode split into multiple jobs.
    I use my PSBootStrap.bat selectively for movie jobs and SickBeard for TV shows.
  • PowerShell has 1000's of cmdlets at your disposal and is a leap from the old Command Prompt days. You can get more in-depth with detecting different types of jobs that you processes such as TV shows and music, which I might give some examples later on.
  • Another more advanced script I am working on at the moment is scraping basic information about movies from IMDB and including them in email notifications. If you have multiple recipients then it will give more context about the jobs you're downloading. Including picture thumbnails, descriptions, year, actors, synopsis, trailer URLs, etc. I will post some examples of this up soon. But for now this should give you a step in the right direction with PS Scripting!
ALbino
Full Member
Full Member
Posts: 214
Joined: October 23rd, 2014, 12:28 am

Re: [Windows] Introduction to PowerShell Post-Scripting!

Post by ALbino »

This is fascinating stuff. Because Batch is so limited I struggled to write script to accommodate my own uniquely specific post processing needs. And in fact, it still has some kinks simply because there's no way around them. It looks like I may need to investigate Powershell to see I can write a cleaner, less buggy, and more useful version of it. Thanks for posting.
Owens
Newbie
Newbie
Posts: 4
Joined: April 1st, 2015, 2:37 am

Re: [Windows] Introduction to PowerShell Post-Scripting!

Post by Owens »

ALbino, you're most welcome! I would love to see what you have written in batch, and perhaps we can translate it into PS. And I agree batch scripting can have its kinks. Even today it still feels like it's 1995 every time I use CMD because of its primitive design. As I mentioned in my first post I ignored PS for years. From the outside it can look a bit daunting but when you start tinkering you'll find its not so bad and actually makes alot of sense the way syntax is structured.
jackkerouac
Newbie
Newbie
Posts: 2
Joined: November 10th, 2015, 10:14 am

Re: [Windows] Introduction to PowerShell Post-Scripting!

Post by jackkerouac »

This is really interesting. I know almost nothing about PowerShell. I wonder if I could convert my batch into PS:

Code: Select all

@ECHO OFF

Echo.
Echo.
Echo ------------------------------
Echo Start of the PostProcessing  Script
Echo.
Echo.

set HandBrakeCLI="C:\Program Files\Handbrake\HandbrakeCLI.exe"
set FileBot="C:\Program Files\FileBot\filebot.exe"
setlocal enableextensions enabledelayedexpansion

set name=%3
set name=%name:"=%

Echo.
Echo ------------------------------
Echo.

pushd %1

Echo.
Echo The file(s) found:

dir /b

Echo.
if exist *.mkv set filetype=.mkv && GOTO Transcode
if exist *.avi set filetype=.avi && GOTO Transcode
if exist *.mp4 set filetype=.mp4 && GOTO Transcode
if exist *.wmv set filetype=.wmv && GOTO Transcode
Echo.
Echo Sorry, no valid video file found
Echo.

GOTO :EOF


:tagTVshow

Echo.
Echo ------------------------------
Echo.

for /f "tokens=* delims= " %%a in ('dir/b *.m4v') do (
set path=%%~DPa
set filename=%%~NXa
for /f "tokens=1-2 delims=-" %%i in ("%%a") do (
set show=%%i
set show=!show:~0,-1!
set S=%%~Nj
set season=!S:~2,2!
set episode=!S:~5,2!
)
)

Echo.
Echo ------------------------------
Echo.

%FileBot% -rename "%path%%filename%" --db TheTVDB -non-strict --format "{n} - {s00e00} - {t}" --output "D:\Downloads\Completed\"

Echo.
Echo ------------------------------
Echo.

::rmdir /s /q %1

GOTO :EOF


:Transcode

Echo.
Echo ------------------------------
Echo.

for /f "tokens=* delims=" %%i in ('dir/b/a-d *%filetype%') do (
     Echo.
     Echo ------------------------------
     Echo.
      %HandBrakeCLI% --input "%%i" --output "%%~ni%.m4v" --preset="Plex - MP4" >nul
)

Echo.
Echo ------------------------------
Echo.


GOTO tagTVshow
jackkerouac
Newbie
Newbie
Posts: 2
Joined: November 10th, 2015, 10:14 am

Re: [Windows] Introduction to PowerShell Post-Scripting!

Post by jackkerouac »

Just in case anyone is interested, here is my PowerShell script.

It does the following:
  • 1. Looks for illegal characters that powershell doesn't like (specifically square brackets) and removes them.

    2. Looks for rar, zip or 7z files and if there, decompresses them using 7z cli.

    3. Looks for avi, flv or mp4 files and if they exist, does 4, then 5.

    4. Uses HandbrakeCLI to convert the files to .m4v files, using the "Plex -MP4" preset that I have found works great with Plex on Roku 3.

    5. Uses Filebot cli to match file to TVDB (I only use this script for movies) and renames the file accordingly, while moving it to my completed directory where Sonarr is watching.
Any thoughts? It seems to work pretty well for me.

Code: Select all

param (
    [string]$var1, 
    [string]$var2, 
    [string]$var3, 
    [string]$var4, 
    [string]$var5
)

write-host “”
write-host “”
write-host “”
write-host “-==================================-”
write-host “-= SABnzbd Poweshell Script 0.0.1 =-”
write-host “-=         by jackkerouac         =-”
write-host “-=          Nov. 11, 2015         =-”
write-host “-==================================-”
write-host “”
write-host “”
write-host “”
write-host "Variables Passed"
write-host “-==============-”
write-host “”
write-host “%1:” $var1
write-host “%2:” $var2
write-host “%3:” $var3
write-host “%4:” $var4
write-host “%5:” $var5
write-host “”
write-host “-==============-”
write-host “”

<# set all the variables #>

$HandBrake = "C:\Program Files\HandBrake\HandBrakeCLI.EXE"
$FileBot = "C:\Program Files\Filebot\filebot.exe"
$finalDir = "D:\Downloads\Completed\"
$fullName = $var3
$workingDir = $var1

<# remove any [ or ] #>

write-host “STEP 1:”
write-host “-=====-”
write-host “”
write-host “Looking for illegal characters.”
write-host “”

if ($workingDir.Contains("]") -or $workingDir.Contains("[")) { 
    write-host ""
    write-host "Illegal character(s) found ... removing."
    write-host ""
    $name = $workingDir -replace "\]", ""
    $name = $name -replace "\[", ""
    write-host “”
    write-host “Done.”
    write-host “”

    <# bug in powershell means we have to move the dir to rename it #>

    Move-Item -LiteralPath $workingDir -Destination $name
    $workingDir = $name
    write-host ""
    write-host "The new working directory is now:" $workingDir
    write-host ""
} else {
    write-host “”
    write-host “No illegal character(s) found, continuing.”
    write-host “”
}


<# check for archived files #>

write-host “”
write-host “STEP 2:”
write-host “-=====-”
write-host “”
write-host “Looking for zip, rar, 7z files in” $workingDir
write-host “”

$archivedFiles = Get-ChildItem -Include @("*.zip","*.rar","*.7z") -Path $workingDir -Recurse

if ($archivedFiles) {
    Set-Location -Path $workingDir
    foreach ($archivedFile in $archivedFiles) { # foreach loop begin
        write-host “”
        write-host “Archived file(s) found.”
        write-host “”
        write-host “Unarchiving” $archivedFile "in" $workingDir
        write-host “”
        & "C:\Program Files\7-Zip\7z.exe" e "$archivedFile" -y -o"$workingDir"
        write-host “”
        write-host “Done.”
        write-host “”
    } # foreach loop end
} else {
    write-host “”
    write-host “No archived file(s) found. Continuing.”
    write-host “”
}

<# check for video files #>

write-host “”
write-host “STEP 3:”
write-host “-=====-”
write-host “”
write-host “Finding mp4, avi, flv files in” $workingDir
write-host “”

$oldVideos = Get-ChildItem -Include @("*.mp4","*.avi","*.flv") -Path $workingDir -Recurse

if ($oldVideos) { 
    Set-Location -Path $workingDir
    foreach ($OldVideo in $oldVideos) {
        write-host “”
        write-host "Found Video:" $OldVideo
        write-host “”

        # $workingDir = [System.IO.Path]::GetDirectoryName($OldVideo)
        $fileName = [System.IO.Path]::GetFileNameWithoutExtension($OldVideo)
        $fileExtension = [System.IO.Path]::GetExtension($OldVideo)
    
        $temp = $var2 -replace ".nzb", ""
        $newName = $temp+$fileExtension
        write-host “Rename File:” $OldVideo "to" $newName
        Rename-Item $OldVideo $newName
        $fileName = [System.IO.Path]::GetFileNameWithoutExtension($newName)
        $fileExtension = [System.IO.Path]::GetExtension($newName)

        write-host “”
        write-host "Done."
        write-host “”

        <# transcode the file #>

        write-host “”
        write-host “STEP 4:”
        write-host “-=====-”
        write-host “”
        write-host "Convert the file" $newName "to M4V."
        write-host ""

        $destination = "$workingDir\$fileName.m4v"

        & "C:\Program Files\HandBrake\HandBrakeCLI.EXE" --input "$workingDir\$newName" --output "$destination" --preset="Plex - MP4"

        <# rename and move the file #>

        write-host “”
        write-host "Done."
        write-host ""
        write-host “STEP 5:”
        write-host “-=====-”
        write-host “”
        write-host "Now renaming and moving."
        write-host “”

        & "C:\Program Files\FileBot\filebot.exe" -rename "$destination" --db TheTVDB -non-strict --format "{n} - {s00e00} - {t}" --output "$finalDir"

        <# delete the entire file and directory #>

        write-host “”
        write-host "Done."
        write-host ""
        write-host “STEP 5:”
        write-host “-=====-”
        write-host “”
        write-host “Cleaing up."
        write-host “”

        Set-Location -Path "D:\Downloads\"
        Remove-Item $workingDir -Force -Recurse

        <# all done - exit code 0 #>

        write-host “”
        write-host "Completed successfully. Exiting."
        write-host ""

        exit 0
    }       
} else {
    write-host ""
    write-host "No video files found. Exiting."
    write-host ""
    exit 0
}

<# should never get here #>

write-host "Reached EOF. Error!"
exit 1
Owens
Newbie
Newbie
Posts: 4
Joined: April 1st, 2015, 2:37 am

Re: [Windows] Introduction to PowerShell Post-Scripting!

Post by Owens »

Great post jackkerouac!

I like how you tackle potential illegal characters before they become a problem later. You can never fully trust file names in bins.

Regarding Write-Host cmdlet, to avoid repeatability and reduce lines of code you can use the `n characters to force a new line where possible.

For example:

Code: Select all

write-host ""
write-host "No video files found. Exiting."
write-host ""
Becomes:

Code: Select all

write-host "`nNo video files found. Exiting.`n"
This will produce the same output displayed with spacing above and below.

Incorporating Filebot is terrific for handling the renaming of TV shows. I was going to show examples of this in future posts but got a bit lazy.

I am working on a push bullet script for notifications which I will post up soon.

Keep up the good work!
Post Reply