Wednesday, August 12, 2015

Use Powershell: Event Viewer with GUI

Powershell scripts enable you to take solve a problem using a repeatable method. The solution is even better when you don't have to run it every time the problem occurs. Lets face it, adding a GUI increases the audience and usability or your scripts. Especially when the proposed solution is used by co-workers who have zero experience in scripting (I mean that in a sincerely nice way).

A personal example: As a former Systems Engineer, and current Support Specialist with my current employer, I find myself and my peers constantly reviewing event logs to see which component of the software blew up. When an environment goes down, efficiency is key. The fallout from unscheduled downtime for an extended amount of time really isn't something anyone needs to deal with (especially as a vendor). That being said, having a set of troubleshooting tools ready to go is vital. As part of a small group of team members supporting a large number of enterprise clients, we need to support each other in anyway we can. It should be obvious where I'm going with this. The interactive Powershell scripts I develop and release to team members are useless if they have to go around digging through a console output and changing variables to get to the root of a problem or perform a simple task. This is where the GUI comes into play. As mentioned before, anything I can do to improve efficiency is a bonus. Here's on example of a tool I through together to view event logs which prevents me from waiting for Server Manager from loading or going in to the appropriate MMC snap-in. For those who have more advanced methods of accessing this information, then just take this for what its worth and enjoy learning to draw a GUI for your solutions.

Final remarks before getting started: While the initial code to these scripts may look daunting, after reviewing the various sections, it should be quite apparent how the windows form is drawn, data is populated, and actions are applied to the buttons (you'll find that actions can be triggered in many different cases, but we're limiting this to a button for now).

Lets Draw a Box:


Step 1 is to load the appropriate assemblies that allow us to draw the .NET windows form elements (the stuff that draws the GUI). We do this by adding the following lines at the beginning of our script since we can't draw the form before these are loaded:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
FYI: [void] suppresses output from loading these assemblies.

After we load the assemblies, we ready to draw our first box. It's recommend you play around with this in Powershell ISE or PowerGUI so you can make changes on the fly and easily execute your script over and over. The next block of script will create a new form. Line 1 creates a the form element. Line 2 defines the text in the title bar. Line 3 sets the size. Line 4 defines the start position. And finally, line 5 displays the form.
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "PS Event Viewer"
$objForm.Size = New-Object System.Drawing.Size(300,350) 
$objForm.StartPosition = "CenterScreen"
[void] $objForm.ShowDialog()

Now click play in ISE or run your script. You should have output similar to below:

Excercise: Change the text title bar text.

Congrats, your first Windows Form built purely in Powershell!

Adding Form Controls:


Adding form controls is very similar to creating the form. We loaded the assemblies and created the form element. Let's dive in and add the controls. First we'll start off with only the script block related to displaying the label control in the form. Line 1 creates a new label form control. Line 2 sets the x,y location coordinates in the parent form element (the form we just created). Line 3 sets the control size. Line 4 is the text being displayed in the control. Line 5 adds the control to the form (think parent/child).
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,10) 
$objLabel.Size = New-Object System.Drawing.Size(350,40) 
$objLabel.Text = "The power of Powershell `nHello World!"
$objForm.Controls.Add($objLabel)

In order to properly demonstrate this, we need to tie this in to the script from the previous section. While this slightly varies from the finished product, it's important to see the basic structure and build on that. You'll also note that the show form line ([void] $objForm.ShowDialog() ) has been moved to the bottom. We don't want to display the windows form until all controls have been created and added. Refer to the inline comments to see how we added this to the script.
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "PS Event Viewer"
$objForm.Size = New-Object System.Drawing.Size(300,350) 
$objForm.StartPosition = "CenterScreen"

######### NEW SECTION #########

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,10) 
$objLabel.Size = New-Object System.Drawing.Size(350,40) 
$objLabel.Text = "The power of Powershell `nHello World!"
$objForm.Controls.Add($objLabel) 

###############################

[void] $objForm.ShowDialog()

Once again, run this script. If you copy and pasted my script above, you should get the following output:
Excercise: Change the text and properties of the label control.

Understanding what we did above, it should be reasonable to assume the next additions to our code will make sense without reviewing each form control. Those familiar with VB, will recognize the controls instantly. Those who aren't, we'll get to that in later posts. Next we'll add a listbox and button control.
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "PS Event Viewer"
$objForm.Size = New-Object System.Drawing.Size(300,350) 
$objForm.StartPosition = "CenterScreen"

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,10) 
$objLabel.Size = New-Object System.Drawing.Size(350,40) 
$objLabel.Text = "Created by Zachary Higgins (higginsps.blogspot.com) `n`nSelect Log Name:"
$objForm.Controls.Add($objLabel) 

######### NEW SECTION #########

$objListBox = New-Object System.Windows.Forms.ListBox 
$objListBox.Location = New-Object System.Drawing.Size(10,60) 
$objListBox.Size = New-Object System.Drawing.Size(260,20) 
$objListBox.Height = 210

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,275)
$OKButton.Size = New-Object System.Drawing.Size(150,23)
$OKButton.Text = "OK"

$objForm.Controls.Add($OKButton)
$objForm.Controls.Add($objListBox) 

###############################

[void] $objForm.ShowDialog()

And the output...

Populating Form Control With Data:


Some form controls can be populated with data that we use to execute blocks of script later on. In this case, we get the names of the event logs available on the server or workstation, and add an entry for in the list box control for each one. First and foremost, we need to understand how we get the objects (log names) being populated in the list box control.

In a Powershell Terminal, execute a get-eventlog -list. You should get something similar to the following (maybe more, maybe less, the beauty of this script is that it doesn't matter):

Now we take this information, and apply this to the form control. Again, the new section of code has been added in between the commented lines. First we declare $eventlogs and populate that variable with our event log names. Note that we're only selecting the log names since that's all we need to reference when we pull the actual events from that log. If we had additional text, it would need stripped away, adding further complication outside the scope of this write-up. Once $eventlogs is declared, we drop into a foreach statement and run through each object (log name) which is declared as $eventlog. Each event log name is added to the listbox control ($objListBox) as an item. This foreach continues until each event log name has been added.
$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "PS Event Viewer"
$objForm.Size = New-Object System.Drawing.Size(300,350) 
$objForm.StartPosition = "CenterScreen"

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,10) 
$objLabel.Size = New-Object System.Drawing.Size(350,40) 
$objLabel.Text = "Created by Zachary Higgins (higginsps.blogspot.com) `n`nSelect Log Name:"
$objForm.Controls.Add($objLabel) 

$objListBox = New-Object System.Windows.Forms.ListBox 
$objListBox.Location = New-Object System.Drawing.Size(10,60) 
$objListBox.Size = New-Object System.Drawing.Size(260,20) 
$objListBox.Height = 210

######### NEW SECTION #########

$eventlogs = (Get-EventLog -List).Log
foreach($eventlog in $eventlogs){
    [void] $objListBox.Items.Add("$eventlog")
}

###############################

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,275)
$OKButton.Size = New-Object System.Drawing.Size(150,23)
$OKButton.Text = "OK"

$objForm.Controls.Add($OKButton)
$objForm.Controls.Add($objListBox) 
[void] $objForm.ShowDialog()

Once again, execute the script thus far and view the new output. We're almost done!

Performing An Action With Form Control:


We have our data populated in the list box form control. Now lets do something with it. We'll apply an action to the "OK" button which outputs all events in the selected log to an out-gridview format. This action can be demonstrated by executing the following cmdlet in a separate Powershell terminal: Get-EventLog -LogName "Application" | Out-GridView. The out-gridview will output something similar to below:

Simply add the next property to the button form control we defined earlier. Quite literally, one line. More advanced actions can be defined here, however let's keep it simple for now.
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,275)
$OKButton.Size = New-Object System.Drawing.Size(150,23)
$OKButton.Text = "OK"

######### NEW SECTION #########

$OKButton.Add_Click({$eventlog=$objListBox.SelectedItem;Get-EventLog -LogName $eventlog | Out-GridView})

###############################


To see how this all fits together, compare this to the full script below. When running this script, selecting the log name and pressing OK, will take that selected log name and output all events to the out-gridview we went over earlier.

This concludes creating a basic GUI in Powershell (with functionality). You can grab the completed script below. I encourage the act of vandalism and overall breakage of scripts when learning. Take this, and create something else!

As always, comment/question/request below!
# CREATED BY ZACHARY HIGGINS
# http://higginsps.blogspot.com

$objForm = New-Object System.Windows.Forms.Form 
$objForm.Text = "PS Event Viewer"
$objForm.Size = New-Object System.Drawing.Size(300,350) 
$objForm.StartPosition = "CenterScreen"

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,10) 
$objLabel.Size = New-Object System.Drawing.Size(350,40) 
$objLabel.Text = "Created by Zachary Higgins (higginsps.blogspot.com) `n`nSelect Log Name:"

$objListBox = New-Object System.Windows.Forms.ListBox 
$objListBox.Location = New-Object System.Drawing.Size(10,60) 
$objListBox.Size = New-Object System.Drawing.Size(260,20) 
$objListBox.Height = 210

$eventlogs = (Get-EventLog -List).Log
foreach($eventlog in $eventlogs){
    [void] $objListBox.Items.Add("$eventlog")
}

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,275)
$OKButton.Size = New-Object System.Drawing.Size(150,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$eventlog=$objListBox.SelectedItem;Get-EventLog -LogName $eventlog | Out-GridView})

$objForm.Controls.Add($objLabel) 
$objForm.Controls.Add($OKButton)
$objForm.Controls.Add($objListBox) 
[void] $objForm.ShowDialog()

Friday, August 7, 2015

Free Script Friday - Basic Server Audit

Free script Friday's are either large Powershell scripts I use, or portions of ones I use that provide some sort of useful functionality. I don't go into detail on these as the write up's would be lengthy. You are free to use these however you would like, and they come with no guarantee. I'm happy to answer questions in the comments below if I can and am happy to help research the question if I can't.

Basic Computer Audit (WMI and Registry)

This audit script uses both WMI and the registry to gather system information to be reported in a console output. Quickly gather base system information in a single click. Furthermore, this can be saved as a script, and ran remotely against servers using invoke-pssession (more on that later). Questions/Comments/Requests? Leave them in the comments below.

cls
# Clear Variables/Set to Defaults
$AuditDate = $(Get-Date  -format "MM/dd/yy HH:mm:ss")
$FQDN = $env:COMPUTERNAME + "." + $env:USERDNSDOMAIN
$SysMake = (Get-WmiObject Win32_ComputerSystem).Manufacturer
$SysModel = (Get-WmiObject Win32_ComputerSystem).Model
If ( $SysModel -like "$SysMake*" ) { $SysInfo = $SysModel } Else { $SysInfo = $SysMake + " " + $SysModel }
$IEver = ((get-item "c:\program files\internet explorer\iexplore.exe" | select -expand versioninfo | fl productversion) | out-string).split(':')[1].split('.')[0]
$OperatingSystem = (Get-WmiObject Win32_OperatingSystem).Caption.substring(10) + " SP " + (Get-WmiObject Win32_OperatingSystem).ServicePackMajorVersion
$TimeZone = (Get-WmiObject Win32_TimeZone).Caption
# Get Memory Information
$SystemMemory = "{0:N0}" -f ((Get-WmiObject Win32_ComputerSystem).TotalPhysicalMemory/1gb)
$MemSlots = [string](@((Get-WmiObject Win32_PhysicalMemory)).count) + " Slots"
$MemSpeed = " at " + [string](@((Get-WmiObject Win32_PhysicalMemory))[0].Speed) + " Mhz"
If ( $MemSpeed -eq " at  Mhz" ) { $MemSpeed = "" }
$MemInfo = $SystemMemory + " GB in " + $MemSlots + $MemSpeed
# Get UAC Info
if((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System").EnableLUA -eq "0") {$UAC = "Disabled"} ELSE { $UAC = "Enabled" }
# Get DEP Info: All Programs or Essential Programs Only
if(!(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\NoExecuteState")) {$DEP = "Essential Programs Only"} ELSE {$DEP = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\NoExecuteState").LastNoExecuteRadioButtonState}
if ($DEP -eq 14012){$DEP="Essential Programs Only"}
if ($DEP -eq 14013){$DEP="All Programs"}
# Get Proc Info
$ProcSummary = @()
$ProcTotal = 0
Get-WmiObject Win32_Processor | Select Name, NumberofCores, NumberofLogicalProcessors | Foreach { $ProcSummary += $_; $ProcTotal += $_.NumberofCores }
If ( $ProcSummary[0].NumberofLogicalProcessors -eq $ProcSummary[0].NumberofCores ) { $HT = " - HT Disabled" } Else { $HT = " - HT Enabled" }
$ProcInfo = [string]($ProcTotal) + " " + $ProcSummary[0].Name.replace("  ","").replace("(R)","").split("@")[0] + $ProcSummary[0].Name.split("@")[1] + $HT

# Write-Output
write-host "Server Summary Audit" -fo Cyan
write-host "==========================" -fo Cyan
write-host "Date:" $AuditDate -fo Green
write-host "FQDN:" $FQDN -fo Green
write-host "System Info:" $SysInfo -fo Green
write-host
write-host "Operating System Audit" -fo Cyan
write-host "==========================" -fo Cyan
write-host "Operating System:" $OperatingSystem
write-host "Timezone:" $TimeZone
write-host "Internet Explorer:" $IEver
write-host "UAC Status:" $UAC
write-host "DEP Status:" $DEP
write-host
write-host "Hardware - Memory" -fo Cyan
write-host "==========================" -fo Cyan
write-host "Memory:" $MemInfo
write-host
write-host "Hardware - Processor" -fo Cyan
write-host "==========================" -fo Cyan
write-host "Processor:" $ProcInfo
write-host
write-host "Hardware - Storage" -fo Cyan
write-host "==========================" -fo Cyan
foreach ($system_vol in (Get-WmiObject Win32_Volume | Where { $_.Name -notlike "\\?\*" } | Sort Name)){
    $system_volname = $system_vol.name
    $system_vollabel = (Get-WmiObject Win32_LogicalDisk | Where { $_.DeviceID -eq $system_vol.DriveLetter }).VolumeName
    $system_VolSize = "{0:N0}" -f ($system_Vol.Capacity/1gb)
    $system_volfs = $system_vol.filesystem
    $system_volpagefile = $system_vol.PageFilePresent
    if (!$system_volpagefile) {$system_volpagefile = "False"} ELSE {$system_volpagefile = "True"}
    if (!$system_volfs) {$system_volfs = "N/A"}
    if ($system_vol.drivetype -eq "5") {$system_volfs = "CDROM"}
    $system_volletter = $system_vol.DriveLetter
    if ($system_vol.drivetype -eq "3") {$system_voldeviceid = ((Get-WmiObject Win32_LogicalDiskToPartition | Where { $_.Dependent -like "*$system_volletter*" }).Antecedent).split("=")[1].replace("""","") }
    Else { $system_volDeviceID = "N/A" }
    # Console Output
    write-host "Volume Name               :" $system_volname
    write-host "Volume Label              :" $system_vollabel
    write-host "Volume Size (GB)          :" $system_volsize
    write-host "File System               :" $system_volfs
    write-host "Device ID                 :" $system_voldeviceid
    write-host "---"
}
write-host
write-host "Hardware - Network Info" -fo Cyan
write-host "==========================" -fo Cyan
$objWin32NAC = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -namespace "root\CIMV2" -Filter "IPEnabled = 'True'" 
foreach ($objNACItem in $objWin32NAC){
    $system_nicdesc = $objNACItem.Description
    $system_nicipv4 = $objNACItem.IPAddress[0]
    $system_nicipv6 = $objNACItem.IPAddress[1]
    $system_nicsub = $objNACItem.IPSubnet[0]
    $system_nicdg = $objNACItem.DefaultIPGateway
    #Console Output
    Write-Host "Description               :" $system_nicdesc
    Write-Host "IP Address (IPv4)         :" $system_nicipv4
    Write-Host "IP Address (IPv6)         :" $system_nicipv6
    Write-Host "Subnet Mask               :" $system_nicsub
    Write-Host "Default Gateway           :" $system_nicdg
    Write-Host "---"
}
write-host

Wednesday, August 5, 2015

Use Powershell: Start Application If Not Running

Script Contents:

$processname = "notepad" 
$processlocation = "c:\windows\system32\notepad.exe" 

$ErrorActionPreference = "SilentlyContinue" 

if (get-process $processname){ 
    write-host "Application running..." -fo Green 
} ELSE { 
    write-host "Application not found... Starting..." -fo Red 
    & $processlocation 
    if (get-process $processname){ 
        write-host "Application Running..." -fo Green 
    } ELSE { 
        write-host "Application failed to start..." -fo Red 
    } 
}

Explanation:

This is a simple script the demonstrates how easily Powershell can be used to perform intelligent tasks. We start out with two variables. The first is the process name ($processname). A quick exercise demonstrating how to gather this information can be performed by opening a shell and running "tasklist" while the process is running. See the example below -

Next we enter in the launch parameters to start the process ($processlocation). If your not sure what this is, you'll have to do a little research. This is what you would type in the run dialog or cmd prompt to start the application/process.

From there we drop into an "if" statement. We use "get-process" to determine if the process is running or not.
If the process is running, we get a nice little output (if not running as a scheduled task or silently) that states the process is already running. If the process isn't running, we invoke the run command specified earlier in $processlocation. From there we drop into a verification similar similar to the parent if statement.
if (get-process $processname){ 
    write-host "Application Running..." -fo Green 
} ELSE { 
    write-host "Application failed to start..." -fo Red 
}
If successfully started, we should get a nice green output stating the application is running. If not, the output will say the contrary.

Tip: When testing, it is best to work on the script in Powershell ISE. This will let you to modify the script and run it on the fly, without having to go back and forth between the shell and your text editor. This will also help with troubleshooting as you will be able to see the errors. To enable error messages, comment out line 5, "$ErrorActionPreference = "SilentlyContinue".

As always, feel free to comment below with requests, suggestions, and questions! Happy scripting.

Tuesday, August 4, 2015

The Basics: Copy Files Using Powershell

Copying files using Powershell may seem unnecessary, however it's important to understand this basic functionality and apply these concepts to more advanced methods. On top of that, no sys admin should be copying files using explorers clunky copy/paste method. The copy-item commandlet offers a powerful and fast way to copy files. In the example below, I demonstrate how to copy a single file, multiple files recursively, and specific file extensions.

Copy Single File

Copy-Item c:\source\file.pdf c:\destination

Copy Multiple Files Recursively

Copy-Item c:\source d:\destination -recurse

Copy Only PDF Files

Copy-Item c:\source\*.pdf c:\destination

Use Powershell: Backing Up Databases

Quick and easy script that will backup all databases (excluding system) in a specified instance to a folder. I'll go ahead and leave the download link up here for those who want to skip the lengthy explanations: Script Download Link...

Key takeaways -

How the connection to the SQL server is created.


1.Load SQL SMO assemblies.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
2.Create connection to SQL server.
$server = New-Object ("Microsoft.SqlServer.Management.Smo.Server") $SQLInstance
3.Get name of databases in SQL instance.
$databases = New-Object Microsoft.SqlServer.Management.Smo.Database
$databases = $server.Databases

Test as you go: At this point, executing "write-host $databases" should give you something like this: [master] [model] [msdb] [tempdb] [testdb1] [testdb2] etc...

Backing up all non-system databases.


1.Differentiating between system/non system databases.
» Executing write-host $databases.issystemobject should give us an output such as: True True True True False
» Now we know that we can differentiate between system databases and nonsystem databases.
2.Run "something" for each non system database.
foreach ($Database in $Databases | where {$_.IsSystemObject -eq $False}){ something..... }
3.Setting up our backup object.(Note there are some variables in there that haven't been mentioned. Their use can be referenced in the full code below).
$bk = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")
$bk.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Database
$bk.BackupSetName = $Database.Name + "_backup_" + $tStamp
$bk.Database = $Database.Name $bk.CompressionOption = 1
$bk.MediaDescription = "Disk" $bk.Devices.AddDevice($BackupFolder + "\" + $Database.Name + "_" + $timestamp + ".bak", "File")
4.Running the backup.
Take note to the TRY/CATCH and the exception message on the CATCH. This is an error handler incase the database fails to backup. On failure, the exception message will be displayed in the console output.
TRY {
    $bk.SqlBackup($server)
    Write-Host "Backup Completed Succesfully: " $Database.Name -ForegroundColor Green}
CATCH {
    write-host $Database.Name " backup failed." -ForegroundColor Red
    write-host "Error: " + $_.Exception.Message -ForegroundColor Red
}


The best way to learn, download the script and play!


# Database Backup Script
# by Zachary Higgins (9.29.2014)

# Load SQL Assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null

# Setup Console and Request User Information
cls
Write-Host Database Backup Script -ForegroundColor Cyan
Write-Host 

# Prompt for manual entry of SQL Instance
$SQLInstance = (Read-host -prompt "Enter SQL Instance Name [server\instance]")
Write-Host "SQL Instance :" $SQLInstance -ForegroundColor Yellow

# Prompt for backup folder
Write-Host "Select backup folder..."
$frmBackup = new-object -com Shell.Application
$Backup = ($frmBackup.BrowseForFolder(0, "Select Folder", 0, "C:\")).self.path
Write-Host "Backup Folder:" $backup -ForegroundColor Yellow
Write-Host
Write-Host

# Console Output "Starting Backups"
Write-Host "Starting Backups--------------------" -ForegroundColor Cyan
Write-Host

# Build SQL Connection
$server = New-Object ("Microsoft.SqlServer.Management.Smo.Server") $SQLInstance
$databases = New-Object Microsoft.SqlServer.Management.Smo.Database
$databases = $server.Databases
$timestamp = Get-Date -UFormat "%y_%m_%d_%H_%M"

foreach ($Database in $Databases | where {$_.IsSystemObject -eq $False}){
 write-host "Database: "$Database.name
 $bk = New-Object ("Microsoft.SqlServer.Management.Smo.Backup")
 $bk.Action = [Microsoft.SqlServer.Management.Smo.BackupActionType]::Database
 $bk.BackupSetName = $Database.Name + "_backup_" + $tStamp
 $bk.Database = $Database.Name
 $bk.CompressionOption = 1
 $bk.MediaDescription = "Disk"
 $bk.Devices.AddDevice($BackupFolder + "\" + $Database.Name + "_" + $timestamp + ".bak", "File")
 TRY
  {$bk.SqlBackup($server)
  Write-Host "Backup Completed Succesfully: " $Database.Name -ForegroundColor Green}
 CATCH 
  {write-host $Database.Name " backup failed." -ForegroundColor Red
  write-host "Error: " + $_.Exception.Message -ForegroundColor Red}
    write-host
}

Write-Host
Write-Host "------------------------------------" -ForegroundColor Cyan
Write-Host 
Write-Host
Write-Host
Write-Host "Finished!"
Script Download Link...