Power Shell

From vwiki
Revision as of 08:48, 23 March 2010 by Sstrutt (talk | contribs) (→‎FTP: Added links)
Jump to navigation Jump to search

Getting Started

Useful Sites

Subject specific useful links are listed in the sections below, the following provide links to installers and general documentation

Installed Version

To check the main installed version use the following command...

 get-host | select version

.

However, if you might have installed something other than the normal RTM or GA release version you'll need to the registry key HKLM\Software\Microsoft\PowerShell\1, which will have the following values of interest...

Value Data Meaning
Install 1 Installed (not version number)
PID 89383-100-0001260-04309 RTM (Release to Manufacturing)
PID 89393-100-0001260-00301 RC2 (Release Candidate 2)

For more info on release version acronyms, see Software Release Life Cycle

Help Commands

Get-Help <cmd>              # Provides help for CmdLets, use wildcards to broaden results.
<object> | Get-Member       # Provides information about an object

Variables

All variable names are prefixed with a $, and are case insensitive (though there's no reason to not use CamelBack notation if that floats your boat).

Apart from a few reserved words and characters there's little restriction on what can be used, though note that this flexibility can cause the occasional issue, whereby PowerShell gets confused as to where a variable name finishes. Variable names can be enclosed in { } in order to delimit them, eg ${varname}</scode>.

Powershell is all about manipulating objects, and its variables are all essentially the same, not being specifically defined as an object, string, integer, etc. Which is normally useful, however sometimes you need to force a variable to contain a data type. Using a prefix of [type] achieves this...

 [string]$result = $PingResult.Status
Notation Data Type
[bool] True / false
[single] Single-precision 32-bit floating point number
[double] Double-precision 64-bit floating point number
[byte] 8-bit unsigned character
[int] 32-bit integer
[long] 64-bit integer
[decimal] 128-bit decimal
[char] Single character
[string] String of characters
[datetime] Date or time
[xml] XML object
[array] Array
[wmi] Windows Management Instrumentation (WMI) instance or collection
[wmiclass] WMI class
[adsi] Active Directory Services object
[Boolean] True or False value

Variable Information

As variables tend to be black boxes that can contain anything or nothing, its often necessary to understand more about one...

Variable Type

$var.GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

Variable Properties and Methods

Get-Member -InputObject $var


Strings

Concatenation +

$strAB = $strA + $strB


Interpolation
Interpolation allows variables to be embedded into a string and to be resolved into their actual values. This works between double quotes, but not between single quotes...

PS E:\> $sub = "replaced"
PS E:\> Write-Output "Variable has been $sub"
Variable has been replaced
PS E:\> Write-Output 'Variable has been $sub'
Variable has been $sub


Search
To search for specific text in a string...

if (Select-String -InputObject $text -Pattern "StringToFind" -Quiet)
{
        # StringToFind found in $text
}


Match (extract)
To extract text that matches a regex...

$res = [regex]::matches($line, "\d{4}-[A-Za-z]{3}-Week\d{1}.log")
if (-not $res.Count)
{
    # No matches found
} else {
    $res1 = $res.Item(1).Value      # 1st match to regex
}


Replace
Basic find and replace can be done with the Replace CmdLet, eg to replace "\" with "\\" in the $query variable...

 $query = $query.Replace("\", "\\")

For proper regular expressions support, use the following syntax

 $query = [regex]::Replace($query, "search", "replace")


Strip Whitespace

 $string = $string.TrimEnd()

Datetime

The object structure: http://msdn.microsoft.com/en-us/library/system.datetime.aspx


Converters

function ConvertLocalToUnix([datetime]$datetime)
{ 
    ($datetime.ToUniversalTime() - ([datetime]'1/1/1970 00:00:00')).TotalSeconds
}

function ConvertUnixtoLocal($sincepoch)
{
    [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($sincepoch))
}

Credentials

Get-Credential

When running commands that require a connection to a remote machine its useful to be able to store a user/pass combination so that you aren't repeatedly prompted every time you run a command. Create a credential object, then supply that in place of a username in a command

PS H:\> $cred = Get-Credential

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
PS H:\> Get-WMIObject -query "SELECT * FROM Win32_OperatingSystem" -credential $cred -computer 159.104.224.167

SystemDirectory : C:\WINDOWS\system32
Organization    : TF
BuildNumber     : 3790
RegisteredUser  : TF
SerialNumber    : 69712-640-3560061-45009
Version         : 5.2.3790

However, this doesn't really help much in a fully scripted situation where you need to supply user and pass in an unattended fashion, for that you also need the help of ConvertTo-SecureString

ConvertTo-SecureString

The following example creates a Credential object that can be used for in place of Get_Credential

$pass = ConvertTo-SecureString $svr.pass -asplaintext -force
$cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist $svr.user,$pass
$wmiobj = Get-WMIObject -query "SELECT * FROM Win32_BIOS" -credential $cred -computer $svr.ip


External Processes

One of PowerShell's greatest failings is its inability to run external commands with any predictability, severely limiting tits scope and often forcing you to run small PowerShell scripts from other scripts rather than having one all-in-control PS script. The following example demonstrates how to run any cmd line...

$cmd = "rrdtool.exe update $rrd $updates"
$proc_res = &$executioncontext.InvokeCommand.NewScriptBlock($cmd)


WMI

Cmdlet for using WMI via PowerShell is Get-WMIObject, for example...

PS H:\> Get-WMIObject -query "Select * from Win32_OperatingSystem"

SystemDirectory : C:\WINDOWS\system32
Organization    :
BuildNumber     : 2600
RegisteredUser  : TF
SerialNumber    : 76487-OEM-0011903-00102
Version         : 5.1.2600

Further useful examples...

# Get OS CPU info (address width, speed, FSB etc
$cpu = Get-WMIObject -query "SELECT * FROM Win32_Processor WHERE DeviceID='CPU0'" -credential $cred -computer $svr

# Get local disks
$drives = Get-WMIObject Win32_LogicalDisk -filter "Description = 'Local Fixed Disk'" -credential $cred -computer $svr


Find Classes and Properties

In order to find the correct class use...

Get-WMIObject -list -credential $cred -computer 159.104.224.167 | Select-String -InputObject {$_.Name} Win32*

To then see all the properties of a class use (if this doesn't work on remote machines (access denied) - it may be due to a known bug in Power Shell v1 whereby Get-WMIObject can't impersonate (or you may just have the wrong credentials)...

Get-WMIObject Win32_BIOS | Format-List *

TechNet article: Windows PowerShell Best Inventory Tool Ever!

Network

Ping

PS H:\> $objPing = New-Object system.Net.NetworkInformation.Ping
PS H:\> $objPing.Send('127.0.0.1')

Status        : Success
Address       : 127.0.0.1
RoundtripTime : 0
Options       : System.Net.NetworkInformation.PingOptions
Buffer        : {97, 98, 99, 100...}

Name/Address Resolution

IP to Name

  • Be aware, where no name can be found, the call throws an exception. If assigning result to a variable, then it seems to return the local hostname, which is odd.
PS H:\> [System.Net.Dns]::GetHostbyAddress("159.104.31.83")

HostName                                Aliases                                 AddressList
--------                                -------                                 -----------
L-STRUTTS1                              {}                                      {159.104.31.83}

Name to IP

PS H:\> [System.Net.Dns]::GetHostAddresses("l-strutts1")

Address           : 1394567327
AddressFamily     : InterNetwork
ScopeId           :
IsIPv6Multicast   : False
IsIPv6LinkLocal   : False
IsIPv6SiteLocal   : False
IPAddressToString : 159.104.31.83

FTP

Things to watch out for...

  • KeepAlive's - Its generally safer to disable keep alives, this causes the FTP session to be dropped after each request. This is less efficient, but leads to more reliable results. If requests don't get completed properly the .NET API gets into a stuck state whereby new FTP requests appear to time-out (though no request actually goes to the FTP server.
  • Inconsistent Results - Results from IIS and non-IIS FTP servers can look different, for example a directory listing on an IIS FTP server results in a basic/raw text result, where as from a non-IIS FTP server this results in HTML rendered text

Useful links

Directory Listing

$site = "ftp://ftp-srv/logfiles"
$user = "Anonymous"
$pass = "Hello"

Write-Host "Get FTP site dir listing..." -nonewline

# Do directory listing
$FTPreq = [System.Net.FtpWebRequest]::Create($site)
$FTPreq.Timeout = 30000                             # msec (default is infinite)
$FTPreq.ReadWriteTimeout = 10000                    # msec (default is 300,000 - 5 mins)
$FTPreq.KeepAlive = $false                          # (default is enabled)
$FTPreq.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
$FTPreq.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory

try
{
    $FTPres = $FTPreq.GetResponse()
}
catch
{
   Write-Host "FAILED: $_"
   Exit
}

Write-Host $FTPres.StatusCode -nonewline
Write-Host $FTPres.StatusDescription 

$list = Receive-Stream $FTPres.GetResponseStream()
$FTPres.Close()

Get

$site = "ftp://ftp-srv/logfiles"
$file = "activity1.log"
$user = "Anonymous"
$pass = "Hello"

Write-Host "Download $file " -nonewline
    
$FTPreq = [System.Net.FtpWebRequest]::Create("$site\$file")
$FTPreq.Timeout = 15000                             # msec (defult is infinite)
$FTPreq.ReadWriteTimeout = 10000                    # msec (defult is 300,000 - 5 mins)
$FTPreq.KeepAlive = $false                          # (default is enabled)
$FTPreq.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
$FTPreq.UseBinary = $true 
$FTPreq.Method = [System.Net.WebRequestMethods+FTP]::DownloadFile
    
try
{
    $FTPres = $FTPreq.GetResponse()
}
catch
{
   Write-Host "FAILED: $_"
   Exit
}
$dest = "$DestDir\$file"
    
Write-Host $FTPres.StatusDescription "Write to $DestDir\$file"
$FTPstream = $FTPres.GetResponseStream()
try
{
    $dest = New-Object IO.FileStream ("$DestDir\$file",[IO.FileMode]::Create)
}
catch
    Write-Host "FAILED: $_"
    $FTPstream.Close()
    $FTPres.Close()
    Exit
}
        
[byte[]]$buffer = New-Object byte[] 1024
$read = 0
do
{
    $read=$FTPstream.Read($buffer,0,1024)
    $dest.Write($buffer,0,$read)
}
while ($read -ne 0)
{
    $dest.Close()
}
$FTPstream.Close()
$FTPres.Close()

MySQL

Connect

function ConnectMySQL([string]$user,[string]$pass,[string]$MySQLHost,[string]$database) { 
  # Load MySQL .NET Connector Objects 
  [void][system.reflection.Assembly]::LoadWithPartialName("MySql.Data") 

  # Open Connection 
  $connStr = "server=" + $MySQLHost + ";port=3306;uid=" + $user + ";pwd=" + $pass + ";database="+$database+";Pooling=FALSE" 
  $conn = New-Object MySql.Data.MySqlClient.MySqlConnection($connStr) 
  $conn.Open() 
  return $conn 
} 

function DisconnectMySQL($conn) {
  $conn.Close()
}

# So, for example...

# Connection Variables 
$user = 'myuser' 
$pass = 'mypass' 
$database = 'mydatabase' 
$MySQLHost = 'database.server.com' 

# Connect to MySQL Database 
$conn = ConnectMySQL $user $pass $MySQLHost $database

Commands

All database operations are done through methods of the MySqlCommand object, the two methods of main interest are...

  • ExecuteNonQuery - Used for queries that don't return any real information, such as an INSERT, UPDATE, or DELETE.
  • ExecuteReader - Used for normal queries that return multiple values. Results need to be received into MySqlDataReader object.
  • ExecuteScalar - Used for normal queries that return a single. The result needs to be received into a variable.

Non-Query

function ExecuteMySQLNonQuery($conn, [string]$query) { 
  $command = $conn.CreateCommand()                  # Create command object
  $command.CommandText = $query                     # Load query into object
  $RowsInserted = $command.ExecuteNonQuery()        # Execute command
  $command.Dispose()                                # Dispose of command object
  if ($RowsInserted) { 
    return $RowInserted 
  } else { 
    return $false 
  } 
} 

# So, to insert records into a table 
$query = "INSERT INTO test (id, name, age) VALUES (1, 'Joe', 33)" 
$Rows = ExecuteMySQLNonQuery $conn $query 
Write-Host $Rows " inserted into database"

Reader Query In theory, this should work, but it doesn't seem to for me. There's something wrong with the while ($results.Read()), in that you end up displaying the last row returned by the SQL query multiple times. Suspect its due to the way that a Reader object only seems to hold a result temporarily.

$query = "SELECT * FROM subnets;"
$cmd = $connMySQL.CreateCommand() 
$cmd.CommandText = $query 
$results = $cmd.ExecuteReader() 
$cmd.Dispose() 
while ($results.Read()) {
  for ($i= 0; $i -lt $reader.FieldCount; $i++) {
      write-output $reader.GetValue($i).ToString()
  }
}

Instead, this approach seems to work more reliably. By loading the data into a dataset, it becomes available for offline manipulation and isn't reliant on the database once the data is loaded in.

function ExecuteMySQLQuery([string]$query) { 
  # NonQuery - Insert/Update/Delete query where no return data is required
  $cmd = New-Object MySql.Data.MySqlClient.MySqlCommand($query, $connMySQL)    # Create SQL command
  $dataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($cmd)      # Create data adapter from query command
  $dataSet = New-Object System.Data.DataSet                                    # Create dataset
  $dataAdapter.Fill($dataSet, "data")                                          # Fill dataset from data adapter, with name "data"              
  $cmd.Dispose()
  return $dataSet.Tables["data"]                                               # Returns an array of results
}

# So, to produce a table of results from a query...
$query = "SELECT * FROM subnets;"
$result = ExecuteMySQLQuery $query
Write-Host "Found" ($result.Length) "rows..."
$result | Format-Table

Scalar Query

Other

$cmd = New-Object MySql.Data.MySqlClient.MySqlCommand("USE $database", $conn)

Exceptions and Error Handling

To control how a script behaves as a result of an exception, modify the $ErrorActionPreference variable, if required.

Value Effect
Continue [Default] Outputs error, but keeps processing
SilentlyContinue No output and it keeps going
Inquire Prompt user for action
Stop Outputs error and halts processing


Basic Error Handler

If you know where the error is likely to occur, then just place an error catcher immediately after it. This doesn't stop the exception appearing on the console, but does allow you to take some action as a result.

if (-not $?) {
    # Handle error here
  }

Try...Catch

Used to catch an exception in a script block where an exception may be likely. Stops the exception being shown on the console

try
{
    # Something in which an exception is likely
}
catch
{
    Write-Host "FAILED: $_"
    Exit
}