Book Image

SQL Server 2014 with PowerShell v5 Cookbook

By : Donabel Santos
Book Image

SQL Server 2014 with PowerShell v5 Cookbook

By: Donabel Santos

Overview of this book

Table of Contents (21 chapters)
SQL Server 2014 with PowerShell v5 Cookbook
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Scripting syntax


We will now dive into the specifics of the PowerShell syntax.

Statement terminators

A semicolon is typically a mandatory statement terminator in many programming and scripting languages. PowerShell considers both a newline and semicolon as statement terminators, although using the newline is more common. The caveat for using the newline is that the previous line must be a complete statement before it gets executed.

Escape and line continuation

The backtick (`) is a peculiar character in PowerShell, and it has a double meaning. You can typically find this character on your keyboard above the left Tab key, and it is on the same key as the tilde ~ symbol.

The backtick is the escape character in PowerShell. Some of the common characters that need to be escaped are as follows:

Escaped character

Description

`n

Newline

`r

Carriage return

`'

Single quote

`"

Double quote

`0

Null

PowerShell also uses the backtick (`) as a line continuation character.

Note

A technical reviewer, Chrissy LeMaire, points out an interesting fact about the backtick. It is used as a line continuation character because it actually escapes the newline. Interesting tidbit!

You may find yourself writing a long chain of commands and may want to put different parts of the command into different lines to make the code more readable. If you do so, you need to make sure to put a backtick at the end of each line you are continuing; otherwise, PowerShell treats the newline as a statement terminator. You also need to make sure that there are not any extra spaces after the backtick:

Invoke-Sqlcmd `
-Query $query `
-ServerInstance $instanceName `
-Database $dbName

Variables

Variables are placeholders for values. Variables in PowerShell start with a dollar ($) sign:

$a=10

By default, variables are loosely and dynamically typed, which means that the variable assumes the data type based on the value of the content:

$a=10
$a.GetType()  #Int32

$a="Hello"
$a.GetType()  #String

$a=Get-Date
$a.GetType()  #DateTime

Note how the data type changes based on the value we assign to the variable. You can, however, create strongly typed variables:

[int]$a=10
$a.GetType()  #Int32

When we have a strongly typed variable, we can no longer just haphazardly assign it any value. If we do, we will get an error:

$a="Hello"

<# Error
Cannot convert value "Hello" to type "System.Int32". Error: "Input string was not in a correct format."
At line:3char:1
+ $a = "Hello"
+ ~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException
#>

We have also mentioned in the previous section that PowerShell is object oriented. Variables in PowerShell are automatically created as objects. Depending on the data type, variables are packaged with their own attributes and methods. To explore what properties and methods are available with a data type, use the Get-Member cmdlet:

Here-string

There may be times when you need to create a string variable that will contain multiple lines of code. You should create these as here-string.

A here-string data type is a string that often contains large blocks of text. It starts with @" and must end with a line that contains only "@ . For here-string terminating characters, make sure this is placed in its own line and there are no other characters and no spaces before or after it:

$query=@"
INSERT INTO SampleXML
(FileName,XMLStuff,FileExtension)
VALUES('$xmlfile','$xml','$fileextension')
"@

String interpolation

When working with strings, you need to remember that using a double quote evaluates enclosed variables, that is, variables are replaced with their values. For example, run the following code snippet:

$today= Get-Date
Write-Host "Today is $today"

#result
#Today is 07/04/2015 19:48:24

This behavior may sometimes cause issues especially if you need to use multiple variables in continuation; as in the following case, where we want to combine $name, underscore (_), $ts, and .txt to create a timestamped filename:

$name = "belle"
$ts = Get-Date -Format yyyy-MMM-dd
$filename = ""$name_$ts.txt""


$filename = "$($name)_$($ts).txt"
Write-Host $filename

This will give an incorrect result because it will look for $name_ and $ts, but since it cannot find $name_, the final filename that we get is 2015-Jul-04.txt and not belle_2015-Jul-04.txt.

To resolve this issue, we can use any of the following to ensure proper interpolation:

$filename = "$($name)_$($ts).txt"
Write-Host $filename

$filename = "${name}_${ts}.txt"
Write-Host $filename

$filename = "{0}_{1}.txt" -f $name, $ts
Write-Host $filename

A single quote, on the other hand, preserves the actual variable name and does not interpolate the value:

$today= Get-Date
Write-Host 'Today is $today'

#result
#Today is $today

You can also store actual commands in a string. However, this is treated as a string unless you prepend it with an ampersand (&), which is PowerShell's invoke or call operator:

$cmd="Get-Process"

$cmd#just displays Get-Process, treated as string
&$cmd#actually executes Get-Process

Operators

The operators used in PowerShell may not be readily familiar to you even if you have already done some programming before. This is because the operators in PowerShell do not use the common operator symbols.

PowerShell

Traditional

Meaning

-eq

==

Equal to

-ne

<> or !=

Not equal to

-match

-notmatch

 

Match using regex; searches anywhere in a string

-contains

-notcontains

 

Check whether the match exists in the collection or array.

-like

-notlike

 

Wildcard match

asterisk (*) for zero or more characters

question mark (?) for any single character

-clike

-cnotlike

 

Case-sensitive wildcard match

-not

!

Negation

-lt

<

Less than

-le

<=

Less than or equal to

-gt

>

Greater than

-ge

>=

Greater than or equal to

-and

&&

Logical and

-or

||

Logical or

-bor

|

Bitwise or

-band

&

Bitwise and

-xor

^

Exclusive or

Note that many operators perform case-insensitive string comparisons by default. If you want to do case-sensitive matching, prepend with c. For example, –ceq, -clike, and -cnotlike.

Displaying messages

Often, we will need to display or log messages as our scripts execute. PowerShell provides a few cmdlets to help us accomplish this:

Get-Command -Name "*Write*" -CommandTypeCmdlet

This will give a list of our Write-related cmdlets:

Cmdlet

Description

Write-Debug

This displays a debug message on the console. It is

typically used with$DebugPreference = "Continue".

Write-Error

This displays a nonterminating error message on the console.

Write-EventLog

This writes a message to the Windows event log.

Write-Host

This displays a string message on the host.

Write-Output

This writes an object to the pipeline.

Write-Progress

This displays a progress bar.

Write-Verbose

This displays a verbose message on the console.

It is typically used with $VerbosePreference = "Continue".

Write-Warning

This displays a warning message on the console.

Although some of these cmdlets seem similar, there are some fundamental differences. For example, Write-Host and Write-Output seem to display the same messages on the screen. The Write-Host cmdlet, however, simply displays a string, but Write-Output writes objects that have properties that can be queried and can eventually be used in the pipeline.

We use Write-Verbose a fair bit in the recipes in this book. Write-Verbose does not automatically display messages on the host. It relies on the $VerbosePreference setting. By default, $VerbosePreference is set to SilentlyContinue, but it can also be set to Continue, which allows us to display messages used with Write-Verbose on the screen:

$VerbosePreference = ""Continue""
$folderName = ""C:\BLOB Files\""

#using PowerShell V2 style Where-Object syntax
Get-ChildItem $folderName |
Where-Object {$_.PSIsContainer -eq $false}  |
ForEach-Object {
   $blobFile = $_
   Write-Verbose ""Importing file $($blobFile.FullName)...""

}
$VerbosePreference = "SilentlyContinue"

This is an elegant way of turning all messages on or off, without needing to change the script.

Comments

Comments are important in any programming or scripting language. Comments are often used to document logic and sometimes a chain of changes to the script.

Single-line comments start with a hash sign (#):

#this is a single line comment

Block comments start with <# and end with #>:

<#
this is a block comment
#>

PowerShell also supports what's called Comment-Based Help. This feature allows you to put a special comment block at the start of your script, or in the beginning of your function, that allows the script or function to be looked up using Get-Help. A sample of this type of comment block is as follows:

<#
.SYNOPSIS
   Creates a full database backup
.DESCRIPTION
   Creates a full database backup using specified instance name and database name
   This will place the backup file to the default backup directory of the instance
.PARAMETER instanceName
   instance where database to be backed up resides
.PARAMETER databaseName
   database to be backed up
.EXAMPLE
   PS C:\PowerShell> .\Backup-Database.ps1 -instanceName"QUERYWORKS\SQL01" -databaseName"pubs"
.EXAMPLE
   PS C:\PowerShell> .\Backup-Database.ps1 -instance "QUERYWORKS\SQL01" -database "pubs"
.NOTES

   To get help:
   Get-Help .\Backup-Database.ps1
.LINK
   http://msdn.microsoft.com/en-us/library/hh245198.aspx
#>

To look up the help, you can simply type a Get-Help cmdlet followed by the script filename or function name:

PS>Get-Help .\Backup-Database.ps1

Special variables

PowerShell also has some special variables. These special variables do not need to be created ahead of time; they are already available. Some of the special variables are as follows:

Special variable

Description

$_

Current pipeline object

$args

Arguments passed to a function

$error

An array that stores all errors

$home

User's home directory

$host

Host information

$match

Regex matches

$profile

Path to a profile, if available

$PSHome

Install a directory of PowerShell

$PSISE

PowerShell Scripting Environment object

$pid

Process ID (PID) of a PowerShell process

$pwd

Present Working Directory

$true

Boolean true

$false

Boolean false

$null

Null value

Special characters

A few more special characters worth noting down are explained in the following table, which you may find in many scripts you work with:

Character

Name

Description

|

Pipe

Command chaining; the output from one command to input to another

`

Backtick

An escape or continuation character

@

At sign

An array

#

Hash sign

A comment

[]

Square brackets

For indexes and strongly typed variables

()

Parentheses

For array members; for calling functions

&

Ampersand

A call operator

*

Start or asterisk

A wildcard character

%

Percent

An alias for Foreach-Object

?

Question mark

An alias for Where-Object

+

Plus

Addition; a string concatenation operator

Conditions

PowerShell supports conditional statements using if-else or switch statements. These two constructs allow you to check for a condition, and consequently execute different blocks of code if the condition is met or not. Note that PowerShell treats null/empty as false.

Let's take a look at an example of an if-else block:

$answer=Read-Host"Which course are you taking?"

if ($answer-eq"COMP 4677")
{
Write-Host"That's SQL Server Administration"
}
elseif ($answer-eq"COMP 4678")
{
Write-Host"That's SQL Server Development"
}
else
{
Write-Host"That's another course"
}

Note

Note that the elseif and else blocks are optional. They don't need to be defined if you do not have a separate code to execute if the condition is not met.

An equivalent switch block can be written for the preceding code:

$answer=Read-Host"Which course are you taking?"

switch ($answer)
{
"COMP 4677"
    {
Write-Host"That's SQL Server Administration"
    }
"COMP 4678"
    {
Write-Host"That's SQL Server Development"
    }
default
    {
Write-Host"That's another course"
    }
}

Note that these two constructs can be functionally equivalent for simple comparisons. The choice to use one over the other hinges on preference and readability. If there are many choices, the switch can definitely make the code more readable.

Regular expressions

Regular expressions, more commonly referred to as regex, specify a string pattern to match. Regex can be extremely powerful and is often used when dealing with massive amounts of text. The area of bioinformatics, for example, tends to rely heavily on regular expressions for gene pattern matching.

Regex can also be quite confusing especially for beginners. It has its own set of patterns and wildcards, and it is up to you to put these together to ensure that you are matching what you need to be matched.

Note

Refer to the Testing regular expressions recipe in Chapter 11, Helpful PowerShell Snippets.

Arrays

Arrays are collections of items. Often, we find ourselves in situations where we need to store a group of items, either for further processing or for exporting:

#ways to create an array
$myArray= @() #empty
$myArray=1,2,3,4,5
$myArray= @(1,2,3,4,5)
#array of processes consuming >30% CPU
$myArray= (Get-Process|Where-Object  CPU -gt30 )

Arrays can either be a fixed size or not. Fixed-size arrays are instantiated with a fixed number of items. Some of the typical methods such as Add or Remove cannot be used with fixed-size arrays:

$myArray= @()
$myArray+= 1,2,3,4,5
$myArray+=6,7,8
$myArray.Add(9) #error because array is fixed size

Removing an item from a fixed array is a little bit tricky. Although arrays have the Remove and RemoveAt methods to remove entries based on a value and index, respectively, we cannot use these with fixed-size arrays. To remove an item from a fixed-size array, we will need to reassign the new set of values to the array variable:

#remove 6
$myArray=$myArray-ne6

#remove 7
$myArray=$myArray-ne7

To create a dynamic-sized array, you will need to declare the array as an array list and add items using the Add method. This also supports removing items from the list using the Remove method:

$myArray=New-ObjectSystem.Collections.ArrayList
$myArray.Add(1)
$myArray.Add(2)
$myArray.Add(3)
$myArray.Remove(2)

We can use indexes to retrieve information from the array:

#retrieve first item
$myArray[0]

#retrieve first 3 items
$myArray[0..2]

We can also retrieve information based on some comparison or condition:

#retrieving anything > 3
$myArray-gt3

Hash tables

A hash table is also a collection. This is different from an array, however, because hashes are collections of key-value pairs. Hashes are also called associative arrays or hash tables:

#simple hash
$simplehash= @{
"BCIT"="BC Institute of Technology"
"CST"="Computer Systems Technology"
"CIT"="Computer Information Technology"
}
$simplehash.Count

#hash containing process IDs and names
$hash= @{}

Get-Process|
Foreach-Object {
   $hash.Add($_.Id,$_.Name)
}

$hash.GetType()

To access items in a hash, we can refer to the hash table variable and retrieve information based on the stored key:

$simplehash["BCIT"]
$simplehash.BCIT

Loops

A loop allows you to repeatedly execute block(s) of code based on some condition. There are different types of loop support in PowerShell. For all intents and purposes, you may use all of these types, but it's always useful to be aware of what's available and doable.

This is a while loop, where the condition is tested at the beginning of the block:

$i=1
while($i-le5)
{
#code block
$i
$i++
}

There is also support for the do while loop, where the condition is tested at the bottom of the block:

$i=1
do
{
#code block
$i
$i++
} while($i-le5)

The for loop allows you to loop a specified number of times, based on a counter that you create in the for header:

for($i=1; $i-le5; $i++)
{
$i
}

There is yet another type of loop, a foreach loop. This loop is a little bit different because it works with arrays or collections. It allows a block of code to be executed for each item in a collection:

$backupcmds=Get-Command -Name "*Backup*" -CommandTypeCmdlet
foreach($backupcmdin$backupcmds)
{
$backupcmd|Get-Member
}

If you're a developer, this code might look very familiar to you. In PowerShell, however, you can use pipelining to make your code more concise:

Get-Command -Name "*Backup*" -CommandTypeCmdlet|
Foreach-Object
{
   #recall that $_ is the current pipeline object
$_|Get-Member
}

Use the foreach loop operator when the collection of objects is small enough so that it can be loaded into memory. For example, an array of 20 string values.

Use the ForEach-Object cmdlet when you want to pass only one object at a time through the pipeline, minimizing the memory usage. For example, a directory containing 10,000 files.

Error handling

When developing functions or scripts, it is important to think beyond just the functionality you are trying to achieve. You also want to handle exceptions, or errors, when they happen. We all want our scripts to gracefully exit if something goes wrong, rather than displaying some rather intimidating or cryptic error messages.

Developers in the house will be familiar with the concept of try/catch/finally. This is a construct that allows us to put the code we want to run in one block (try), exception handling code in another (catch), and any must-execute housekeeping blocks in a final block (finally):

$dividend =20
$divisor =0

try
{
    $result = $dividend/$divisor
}
catch
{
Write-Host ("======"*20)
Write-Host"Exception $error[0]"
Write-Host ("======"*20)

}
finally
{
Write-Host"Housekeeping block"
Write-Host"Must execute by hook or by crook"
}