Monday, October 12, 2015

Powershell Script - Set-UserPassword - Remotely sets local account passwords

Here's a great script to change passwords in bulk on many servers. I've added verbose and error output for logging purposes as well as time/date stamping for when actual password setting occurs.
PS C:\> 'server1','server2','Badserver' | Set-UserPassword -Username 'test' -Password 'pass123' -Verbose
VERBOSE: Processing server 'server1'...
VERBOSE: Connected to server 'server1'...
VERBOSE: Retrieved user objects from server 'server1'...
VERBOSE: Found user 'test' from server 'server1'...
10/12/2015 14:41:31 - Successfully changed password for user 'test' on server 'server1'
VERBOSE: Processing server 'server2'...
VERBOSE: Connected to server 'server2'...
VERBOSE: Retrieved user objects from server 'server2'...
WARNING: ERROR: No user 'test' on server 'server2'
VERBOSE: Processing server 'Badserver'...
WARNING: ERROR: Failed to connect to server 'Badserver'
PS C:\>


Monday, October 5, 2015

Powershell Quick Script - Get emailed when a specific account is locked

Here's something quick and simple which sends you an email when an AD user account is locked out. The script just runs in a loop, polling ever 30 seconds and then finishes once the account is locked out. This requires a SMTP relay or receive connector on your exchange server to receive email.


$username = 'myuser'
While(Get-AdUser $username).enabled){Start-Sleep -Seconds 30}
Send-MailMessage -To 'admin@domain.com' -From 'YourPSscript@domain.com' -Subject "Account locked out - $username" `
  -SmtpServer exchangeserver -Body "Your account $username"

Monday, August 10, 2015

Powershell Script - Push-Patch - remote installation for KB hotfix, MSI, MSU, or EXE files

Here's a wickedly useful function I threw together based on a strong need to push emergency patches to thousands of servers without the standard route of using a patching deployment system like SCCM, WSUS, Altiris, or HPSA.  All of these tools have their strengths and are generally considered superior to just manually pushing patches, but the sheer versatility of ease of being able to quickly deploy patches is what I required to make tight patching deadlines.

I recommend using this function with Get-HotFix, or Get-WmiObject -class Win32_product for a list of installed patches/software to determine if installation is needed and/or was successful.  You can also come in after the push and look at the log files on the servers in an automated fashion to see how things went.

This script does require PSRemoting to be enabled on target servers, which is true by default on all Windows Server 2012 R2 installs, but needs to be turned on either by GPO or by hand on previous versions.  There's a built in workaround if you pull down PSExec which will work on older servers without PSRemoting.

Tuesday, August 4, 2015

Powershell Script - Get-LocalUsers - Remotely query all local users and details

Here's a lovely script that runs with parallel pipeline queries using the [ADSI] .NET class to remotely query SAM to build nice object outputs which are great for security audits.  Here's some sample output:

PS C:\> 'Server1','Server2' | Get-LocalUsers


Server          : Server1
UserName        : Administrator
Active          : True
PasswordExpired : False
PasswordAgeDays : 16
LastLogin       : 6/3/2015 6:34:27 PM
Groups          : Administrators
Description     : Built-in account for administering the computer/domain

Server          : Server1
UserName        : Guest
Active          : False
PasswordExpired : False
PasswordAgeDays : 0
LastLogin       : 
Groups          : Guests
Description     : Built-in account for guest access to the computer/domain

Server          : Server2
UserName        : Administrator
Active          : True
PasswordExpired : False
PasswordAgeDays : 1
LastLogin       : 3/5/2015 7:28:14 PM
Groups          : Administrators
Description     : Built-in account for administering the computer/domain

Server          : Server2
UserName        : Guest
Active          : False
PasswordExpired : False
PasswordAgeDays : 0
LastLogin       : 
Groups          : Guests
Description     : Built-in account for guest access to the computer/domain


PS C:\> 

And the code:

Wednesday, July 22, 2015

Powershell Script - Get-ServerHDDinfo - Remotely pull hdd size info quickly

I wrote two versions of this function for different scenarios.  The first version is much faster but requires PS remoting enabled on all target servers.  the second version will work with any server with remote WMI enabled which means it will support older 2003 servers much more easily.

The core of the functions is the same: pull the Win32_Volume WMI class and build a report from hdd information.  The first function remotely executes a script block which builds it's own report and then sends it back to the calling computer, while the second version calls each computer's WMI directly and builds one report for all servers.  Even with Get-WmiObject's ability to query multiple servers at once, it still can't match the speed of Invoke-Command, which leverages the faster CIM framework.


Version 1 - Much faster Invoke-Command Version


Version 2 - Slower WMI

Tuesday, July 21, 2015

Beginner's Guide - Working with lists (part 4)

In this final installment of my 4 part series on working with lists, we'll cover advanced Select-Object statements, utilizing the power of computed expressions with custom names for each column (parameter).

In case you missed the first three parts, you can check them out here:

Part 1 - simple string arrays
Part 2 - object arrays
Part 3 - building custom object arrays


Custom Parameter Names

From Part 2 - Selecting, we covered how to return only a subset of columns (parameters) from our object arrays and we can even order the parameters so they appear the way we want them in a table or CSV file, etc.  Custom parameter names takes this one step further and instead of just selecting which parameters we want to output, we also choose new names for them.

To do this, we use hashtables, one for each parameter.  Within the hashtable, we specify two name-value pairs.  The first one is "name" (or "n" is also acceptable) with the value equal to whatever name we want to give the new parameter.  The second pair is "expression" (or "exp", or even just "e") with the value of the old parameter's name in the form of a powershell expression.

Below you see an example where we replace the default "dir" output with our own names.  Don't be afraid of the dollar-underscore part of the statement, we'll cover this later; for now, just understand that it's a special variable that represents the current object (record) in the table.  The select statement transforms the incoming data object by object and the $_ variable just means the current one it's processing.

PS C:\> dir


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-r---        7/19/2015   3:39 AM                Program Files
d-r---        7/21/2015  12:49 PM                Program Files (x86)
d-r---        7/19/2015   3:39 AM                Users
d-----        7/19/2015   3:40 AM                Windows
-a----         7/2/2015   7:39 PM           2544 myserverstatus.csv
-a----         7/2/2015   7:39 PM           2544 myserverstatus.xml


PS C:\> dir |select Name

Name
----
Program Files
Program Files (x86)
Users
Windows
myserverstatus.csv
myserverstatus.xml


PS C:\> dir | select @{n="Nombre"; e={$_.name}}

Nombre
------
Program Files
Program Files (x86)
Users
Windows
myserverstatus.csv
myserverstatus.xml


PS C:\> dir | select @{n="Nombre"; e={$_.name}}, @{n="Type"; e={$_.mode}}

Nombre              Type
------              ----
Program Files       d-r---
Program Files (x86) d-r---
Users               d-r---
Windows             d-----
myserverstatus.csv  -a----
myserverstatus.xml  -a----


PS C:\> dir | select @{n="Nombre"; e={$_.name}}, @{n="Type"; e={$_.mode}}, @{n="File Write Time"; e={$_.LastWriteTime}}

Nombre              Type   File Write Time
------              ----   ---------------
AMD                 d----- 7/19/2015 3:38:16 AM
EFI                 d----- 5/23/2015 4:52:28 AM
Games               d----- 6/20/2015 8:13:11 PM
HyperV              d----- 7/7/2015 3:58:26 PM
Intel               d----- 6/24/2015 6:59:47 PM
MSI                 d----- 6/24/2015 6:50:19 PM
PerfLogs            d----- 7/10/2015 4:04:22 AM
Program Files       d-r--- 7/19/2015 3:39:38 AM
Program Files (x86) d-r--- 7/21/2015 12:49:26 PM
Users               d-r--- 7/19/2015 3:39:41 AM
Windows             d----- 7/19/2015 3:40:35 AM
Windows.old         d----- 7/19/2015 4:36:26 AM
myserverstatus.csv  -a---- 7/2/2015 7:39:18 PM
myserverstatus.xml  -a---- 7/2/2015 7:39:27 PM


PS C:\>


As you can see the expression part of the hashtable has it's own curly brackets.  You can use this template to help you create your own custom parameter names:

$datatable | select @{n="New Parameter Name 1"; e={$_.OldName1}}, @{n="New Parameter Name 2"; e={$_.OldName2}}, @{n="New Parameter Name X"; e={$_.OldNameX}}


Rounding and changing units

Another very common use for using custom selects is to change data units and rounding fractions.  Using another "dir" example, say we want to convert the units to GB or TB, as working with bytes can be annoying.

PS C:\> dir C:\HyperV\VHD\ | select Name, Length

Name                  Length
----                  ------
BVadm1.vhdx      11446255616
BVDC1.vhdx       10305404928
BVServer10.vhdx   9030336512
BVSMA1.vhdx       9936306176
BVSQL1.vhdx      10741612544
BVSQL1_DATA.vhdx  1077936128
DBadm2.vhdx      18291359744
Dev10.vhdx       18996002816


PS C:\> dir C:\HyperV\VHD\ | select Name, @{n="Size (GB)"; e={$_.length / 1GB}}

Name               Size (GB)
----               ---------
BVadm1.vhdx      10.66015625
BVDC1.vhdx        9.59765625
BVServer10.vhdx   8.41015625
BVSMA1.vhdx       9.25390625
BVSQL1.vhdx      10.00390625
BVSQL1_DATA.vhdx  1.00390625
DBadm2.vhdx      17.03515625
Dev10.vhdx       17.69140625


PS C:\> dir C:\HyperV\VHD\ | select Name, @{n="Size (GB)"; e={[math]::round($_.length / 1GB,2)}}

Name             Size (GB)
----             ---------
BVadm1.vhdx          10.66
BVDC1.vhdx             9.6
BVServer10.vhdx       8.41
BVSMA1.vhdx           9.25
BVSQL1.vhdx             10
BVSQL1_DATA.vhdx         1
DBadm2.vhdx          17.04
Dev10.vhdx           17.69


PS C:\>
Notice how I can mix and match normal parameters with custom parameters, and that they appear in the order I list them in the select statement.


Computing new parameters

Perhaps the most powerful use of custom selects is to compute data from other parameters.  When possible, using custom selects is the fastest way to transform and compute data from lists. It's much better than writing a whole script to create a new object array and build new objects with different parameters.
PS C:\> dir | select Name,
>>> @{n="Directory"; e={$_.mode -like "d*"}},
>>> @{n="Name Length"; e={$_.name.length}},
>>> @{n="Days Old"; e={((get-date) - $_.creationTime).days}}

Name                Directory Name Length Days Old
----                --------- ----------- --------
AMD                      True           3       31
HyperV                   True           6       31
Intel                    True           5       27
Program Files            True          13       11
Program Files (x86)      True          19       11
Users                    True           5       11
Windows                  True           7       11
Windows.old              True          11        2
myserverstatus.csv      False          18       19
myserverstatus.xml      False          18       19


PS C:\> gwmi win32_logicaldisk | select @{n="Letter"; e={$_.deviceid[0]}}, 
>>> @{n="Used Space (TB)"; e={[math]::round(($_.size - $_.freespace)/1TB,3)}},
>>> @{n="Percent Free"; e={[math]::round(100 * $_.freespace / $_.size,0)}}

Letter Used Space (TB) Percent Free
------ --------------- ------------
     C           0.022           82
     D           0.017           86
     I           2.202           27
     M           3.917           22


PS C:\>
As you can see, using custom selects with object arrays is a great way to extract exactly what you need from source data without doing too much work. Also note that you can put each parameter definition on a new line as long as you end the current line with a comma as I did here.

Sunday, July 12, 2015

My son was born!

Apologizes for not posting any new content, I've been busy with my second child.  I'll post part 4 of my beginners series on lists within a few days.


Tuesday, July 7, 2015

Beginner's Guide - Working with lists (part 3)

This is the third installment of my beginner's guide to working with lists.  Part 1 covered simple string arrays and Part 2 dived into object arrays and working with them.  We'll cover building custom object arrays from scratch in this section.

Making Custom Objects

Since we already know a bit about working with arrays in general and we know that object arrays are just ordered collections of objects, we are going to spend most of this covering building custom objects.  There are two ways to make a custom object.

The first way is to define an empty object and then add parameters, along with data types and data, one at a time.  This way is more common because order of the parameters is preserved so when you output your object arrays, they columns (parameters) are in the in the order you'd like and you don't have to explicitly name an order of your preference.

The second way is quicker, as you add all of the parameters, data types, and data at once, but you lose the order in which your parameters are added.  This isn't a problem if you explicitly name your output order with either a select or format-table statement, but you just have to remember to do that.

Lets define some objects using the first way and then add them together into an array.
PS C:\> $person1 = New-Object psobject
PS C:\> Write-Output $person1

PS C:\> $person1 | Add-Member -Type NoteProperty -Name "First" -Value "Bob"
PS C:\> $person1 | Add-Member -Type NoteProperty -Name "Last" -Value "Smith"
PS C:\> $person1 | Add-Member -Type NoteProperty -Name "Age" -Value 40
PS C:\> $person1 | Add-Member -Type NoteProperty -Name "isAlive" -Value $true
PS C:\> $person1 | Add-Member -Type NoteProperty -Name "Birthdate" -Value ([datetime]"4/2/75")
PS C:\> Write-Output $person1


First     : Bob
Last      : Smith
Age       : 40
isAlive   : True
Birthdate : 4/2/1975 12:00:00 AM

Let's make another person object.
PS C:\> $person2 = New-Object psobject
PS C:\> $person2 | Add-Member -Type NoteProperty -Name "First" -Value "Mary"
PS C:\> $person2 | Add-Member -Type NoteProperty -Name "Last" -Value "Jones"
PS C:\> $person2 | Add-Member -Type NoteProperty -Name "Age" -Value 45
PS C:\> $person2 | Add-Member -Type NoteProperty -Name "isAlive" -Value $true
PS C:\> $person2 | Add-Member -Type NoteProperty -Name "Birthdate" -Value ([datetime]"2/21/70")
PS C:\> Write-Output $person2


First     : Mary
Last      : Jones
Age       : 45
isAlive   : True
Birthdate : 2/21/1970 12:00:00 AM
Lets now combine the two objects into a new object array. As we saw in part 1, the notation of using @() will define an empty array.
PS C:\> $people = @($person1,$person2)

PS C:\> $people.GetType()                    #We created a new object array with two records

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\> $people.Count
2

PS C:\> $people | Format-Table -AutoSize

First Last  Age isAlive Birthdate
----- ----  --- ------- ---------
Bob   Smith  40    True 4/2/1975 12:00:00 AM
Mary  Jones  45    True 2/21/1970 12:00:00 AM

The second way: Hashtable

As I briefly covered in part 1, hashtables are collections of unordered name & value pairs.  The second way to define a custom object is to pass in a parameter list in the form of a hashtable.
PS C:\> $person3 = New-Object psobject -Property @{"First"="Sandra"; "Last"="Johnson"; "Age"=85; "isAlive"=$false; "Birthdate"=([datetime]"7/1/30")}
PS C:\> Write-Output $person3


Birthdate : 7/1/1930 12:00:00 AM
Last      : Johnson
First     : Sandra
Age       : 85
isAlive   : False
As you can see, I've defined the hashtable as part of the parameter for new-object. Lets create another person.
PS C:\> $HT = @{"First"="James"; "Last"="Brown"; "Age"=95; "isAlive"=$false; "Birthdate"=([datetime]"3/19/20")}
PS C:\> $person4 = New-Object psobject -Property $HT
PS C:\> Write-Output $person4


isAlive   : False
First     : James
Age       : 95
Birthdate : 3/19/2020 12:00:00 AM
Last      : Brown

Again, it's random as to how parameters are added. Now lets combine them along with our first list.
PS C:\> $morepeople = @()            #Since the first object I'm combining is already an object array, I could have skipped this line.
PS C:\> $morepeople += $people       #adds the two records from the first object array
PS C:\> $morepeople += $person3      #the plus-equal operator appends the object array with another record
PS C:\> $morepeople += $person4
PS C:\> $morepeople | Format-Table First,Last,Age,isAlive,Birthdate -AutoSize   #Here is where I explicitly define the order.

First  Last    Age isAlive Birthdate
-----  ----    --- ------- ---------
Bob    Smith    40    True 4/2/1975 12:00:00 AM
Mary   Jones    45    True 2/21/1970 12:00:00 AM
Sandra Johnson  85   False 7/1/1930 12:00:00 AM
James  Brown    95   False 3/19/2020 12:00:00 AM



In part 4, we'll go into more advanced ways of selecting parts of existing object arrays to make new ones using custom expressions.

Thursday, July 2, 2015

Beginner's Guide - Working with lists (part 2)

In part 1 of this series, we covered string arrays and briefly touched on hash tables.  In this tutorial we'll cover how to use and define custom lists (object arrays).  All data flows in and out of cmdlets are objects.  Even plain numbers or strings are technically objects.  And most of the time, you'll be working with more than one of these objects in the form of an array of objects, which is why this topic is pretty crucial for understanding and working with Powershell effectively.

As we covered in part 1, if you think of an excel spreadsheet as a table of data or in Powershell as an array of objects.  Each column has a header which is known as Properties and a data type, while rows or records are known as Objects (or PSCustomObject).  Here are some examples of Object arrays.
PS C:\> $serverstatus = import-csv C:\ServerStatus.csv
PS C:\> $serverstatus | Format-Table -AutoSize

ServerName TimeStamp           RDPPort Results
---------- ---------           ------- -------
server1    2015-06-26T09:35:51 3389    Up
server2    2015-06-26T09:35:52 3389    Up
server3    2015-06-26T09:35:53 3389    Up
server4    2015-06-26T09:35:54 3389    Up
server5    2015-06-26T09:35:55 3389    Up


PS C:\> $serverstatus.GetType()         #[] indicates array, so this is a data type of Object Array

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS C:\> $serverstatus[0]                 #from part 1 - indexing the array, 0 = the first record

ServerName TimeStamp           RDPPort Results
---------- ---------           ------- -------
server1    2015-06-26T09:35:51 3389    Up


PS C:\> $serverstatus[0].ServerName.GetType()      #The data type of the property server name is of type String

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object


PS C:\> $serverstatus[0].TimeStamp             # referencing the 
2015-06-26T09:35:51


PS C:\> $cdrive = Get-ChildItem                #dir is the alias for Get-ChildItem.
PS C:\> $cdrive | Format-Table -AutoSize


    Directory: C:\


Mode          LastWriteTime Length Name
----          ------------- ------ ----
d-----  6/30/2015   5:01 PM        HyperV
d-----  5/23/2015   6:06 AM        PerfLogs
d-r---   7/2/2015   5:23 PM        Program Files
d-r---   7/1/2015   6:54 PM        Program Files (x86)
d-r---  6/20/2015   7:05 PM        Users
d-----  6/27/2015   8:20 AM        Windows



PS C:\> $cdrive.GetType()                     #again, we can see it's an Object Array

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array



PS C:\> $cdrive | Get-Member                 #By default, get-member is only looking at the first object in the array


   TypeName: System.IO.DirectoryInfo

Name                      MemberType     Definition
----                      ----------     ----------
Mode                      CodeProperty   System.String Mode{get=Mode;}
Create                    Method         void Create(), void Create(System.Security.AccessControl.DirectorySecurity ...
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateSubdirectory        Method         System.IO.DirectoryInfo CreateSubdirectory(string path), System.IO.Director...
Delete                    Method         void Delete(), void Delete(bool recursive)
EnumerateDirectories      Method         System.Collections.Generic.IEnumerable[System.IO.DirectoryInfo] EnumerateDi...
EnumerateFiles            Method         System.Collections.Generic.IEnumerable[System.IO.FileInfo] EnumerateFiles()...
EnumerateFileSystemInfos  Method         System.Collections.Generic.IEnumerable[System.IO.FileSystemInfo] EnumerateF...
Equals                    Method         bool Equals(System.Object obj)
GetAccessControl          Method         System.Security.AccessControl.DirectorySecurity GetAccessControl(), System....
GetDirectories            Method         System.IO.DirectoryInfo[] GetDirectories(), System.IO.DirectoryInfo[] GetDi...
GetFiles                  Method         System.IO.FileInfo[] GetFiles(string searchPattern), System.IO.FileInfo[] G...
GetFileSystemInfos        Method         System.IO.FileSystemInfo[] GetFileSystemInfos(string searchPattern), System...
GetHashCode               Method         int GetHashCode()
GetLifetimeService        Method         System.Object GetLifetimeService()
GetObjectData             Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, Sys...
GetType                   Method         type GetType()
InitializeLifetimeService Method         System.Object InitializeLifetimeService()
MoveTo                    Method         void MoveTo(string destDirName)
Refresh                   Method         void Refresh()
SetAccessControl          Method         void SetAccessControl(System.Security.AccessControl.DirectorySecurity direc...
ToString                  Method         string ToString()
PSChildName               NoteProperty   string PSChildName=AMD
PSDrive                   NoteProperty   PSDriveInfo PSDrive=C
PSIsContainer             NoteProperty   bool PSIsContainer=True
PSParentPath              NoteProperty   string PSParentPath=Microsoft.PowerShell.Core\FileSystem::C:\
PSPath                    NoteProperty   string PSPath=Microsoft.PowerShell.Core\FileSystem::C:\AMD
PSProvider                NoteProperty   ProviderInfo PSProvider=Microsoft.PowerShell.Core\FileSystem
Attributes                Property       System.IO.FileAttributes Attributes {get;set;}
CreationTime              Property       datetime CreationTime {get;set;}
CreationTimeUtc           Property       datetime CreationTimeUtc {get;set;}
Exists                    Property       bool Exists {get;}
Extension                 Property       string Extension {get;}
FullName                  Property       string FullName {get;}
LastAccessTime            Property       datetime LastAccessTime {get;set;}
LastAccessTimeUtc         Property       datetime LastAccessTimeUtc {get;set;}
LastWriteTime             Property       datetime LastWriteTime {get;set;}
LastWriteTimeUtc          Property       datetime LastWriteTimeUtc {get;set;}
Name                      Property       string Name {get;}
Parent                    Property       System.IO.DirectoryInfo Parent {get;}
Root                      Property       System.IO.DirectoryInfo Root {get;}
BaseName                  ScriptProperty System.Object BaseName {get=$this.Name;}
LinkType                  ScriptProperty System.Object LinkType {get=[Microsoft.PowerShell.Commands.InternalSymbolic...
Target                    ScriptProperty System.Object Target {get=[Microsoft.PowerShell.Commands.InternalSymbolicLi...



PS C:\> $processes = Get-Process                          #Gets the current active processes
PS C:\> $processes | Format-Table -AutoSize               #there are many more properties per object, but only these 8 are being shown

Handles NPM(K)  PM(K)  WS(K)   VM(M)    CPU(s)    Id ProcessName
------- ------  -----  -----   -----    ------    -- -----------
    480     29  69460  37712 2097402      2.41  6236 ApplicationFrameHost
    275     34  90344  71268     396    275.47  3524 chrome
    206     21  28128  13200     198      1.48  3600 chrome
    206     20  27776  15156     196      1.61  3740 chrome
    208     21  29668  12836     198      1.45  3744 chrome
   4301    146 232976 253580    1291 16,956.36  5072 chrome
     94      9   4424   3572 2097235      1.45  3108 conhost
    114     11  10908  10148 2097277      7.80  6264 conhost
    114     10   4844  11348 2097246      2.09  9760 conhost
    354     14   1308   1512 2097204      2.98   540 csrss
    705     49  13956   6252 2097341    122.59   692 csrss
    238     12   2784   2460 2097253      0.39  5636 dllhost
   1064     65 203812  45616 2097693    617.16  1204 dwm
   2791    211 158364 125988 2098011    577.27  3948 explorer
     37      7    924   1300 2097209      0.17  6824 fontdrvhost
      0      0      0      4       0               0 Idle
    234     15   2800   3032      69      0.16  8136 LMS
   1759     30   8056  13904 2097205     65.52   824 lsass
   1603     68  95844  43124 2098071  2,758.66  8128 mmc
    353     35  26680   7628     592      2.41  6984 MOM
    596     67 123360  80576 2097484  1,872.06  2420 MsMpEng
    962     34  41648  22180 2097413    477.55   716 mstsc
    269     14   9744   2324 2097206    200.13  3000 NisSrv
    996     67 130260 116112 2097932     12.22  3628 powershell
   1063     82 421540 449268 2098281     62.19 11572 powershell
   1173     47  37088  52140 2097484     59.64  4048 RuntimeBroker
    112      8   1172   5684 2097186      0.00 10644 SearchFilterHost
    813     75  43600  44424 2097584     94.95  4148 SearchIndexer
    735     25   6720  20576 2097495      0.11 12072 SearchProtocolHost
    966     82  94528 103820   33439      7.97  4460 SearchUI
    291     12   4316   4884 2097189     22.48   816 services
    864     43  88108  55028     405  3,876.80  3572 ShellExperienceHost
    408     18   5588  14368 2097295      7.41  3612 sihost
     49      3    336    316 2097156      0.08   344 smss
    176     13   3288   3124 2097252      1.23  7880 splwow64
    466     31   8920   8808 2097258     22.00  1524 spoolsv
    915     53   9596  13912 2098446     12.58   436 svchost
   2388     75  28756  49576 2097395  2,164.47   812 svchost
    644     22   6808  11160 2097222     12.31   932 svchost
    703     23   6044   8404 2097238     60.58   988 svchost
    937     31  13752  16112 2097246  2,608.06  1064 svchost
    709     38   6976  10896 2097248      7.03  1076 svchost
    593     31   7184   9836 2101998    231.47  1304 svchost
    220     17   2256   3872 2097197      3.28  1316 svchost
    516     44  18376  20044 2097281     37.47  1356 svchost
    469     25  19024  25668 2097318     71.41  2284 svchost
    260     17   6612  11388 2097862     26.20  2368 svchost
    201     14   3328   2440 2097239      0.03  3620 svchost
   1647      0    448  63616     129  2,523.84     4 System
    343     30   6412   9044 2097515      2.13  3696 taskhostw
    413     27  16992  25596 2097361  1,212.61  2476 Taskmgr
    145     12   1904   3360 2097210      2.38  7740 unsecapp
    568     26  16380  18020 2097282    574.36  2376 vmms
    507     27  41316   9784 2097276  1,244.92  2244 vmwp
     84      8    904   1032 2097194      0.05   684 wininit
    213     10   1864   3444 2097213      0.47   772 winlogon
    356     18   9144  12744 2097240  2,362.16  2732 WmiPrvSE
    143     10   2480   5460 2097189      0.41  5716 WmiPrvSE
    480     23   7232  18772 2097297      0.95  4156 WSHost


PS C:\> $processes.GetType()               #Again, you can see it's an Object Array

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS C:\> $processes[0] | Format-List *      #Now we get to see the full list of properties per object, similar to Get-Member

__NounName                 : Process
Name                       : ApplicationFrameHost
Handles                    : 480
VM                         : 2199285780480
WS                         : 38617088
PM                         : 71127040
NPM                        : 29232
Path                       : C:\Windows\system32\ApplicationFrameHost.exe
Company                    : Microsoft Corporation
CPU                        : 2.40625
FileVersion                : 10.0.10130.0 (fbl_impressive.150522-2224)
ProductVersion             : 10.0.10130.0
Description                : Application Frame Host
Product                    : Microsoft® Windows® Operating System
Id                         : 6236
PriorityClass              : Normal
HandleCount                : 480
WorkingSet                 : 38617088
PagedMemorySize            : 71127040
PrivateMemorySize          : 71127040
VirtualMemorySize          : 262524928
TotalProcessorTime         : 00:00:02.4062500
BasePriority               : 8
ExitCode                   :
HasExited                  : False
ExitTime                   :
Handle                     : 5628
SafeHandle                 : Microsoft.Win32.SafeHandles.SafeProcessHandle
MachineName                : .
MainWindowHandle           : 266852
MainWindowTitle            : Calculator
MainModule                 : System.Diagnostics.ProcessModule (ApplicationFrameHost.exe)
MaxWorkingSet              : 1413120
MinWorkingSet              : 204800
Modules                    : {System.Diagnostics.ProcessModule (ApplicationFrameHost.exe),
                             System.Diagnostics.ProcessModule (ntdll.dll), System.Diagnostics.ProcessModule
                             (KERNEL32.DLL), System.Diagnostics.ProcessModule (KERNELBASE.dll)...}
NonpagedSystemMemorySize   : 29232
NonpagedSystemMemorySize64 : 29232
PagedMemorySize64          : 71127040
PagedSystemMemorySize      : 456312
PagedSystemMemorySize64    : 456312
PeakPagedMemorySize        : 72245248
PeakPagedMemorySize64      : 72245248
PeakWorkingSet             : 66281472
PeakWorkingSet64           : 66281472
PeakVirtualMemorySize      : 266883072
PeakVirtualMemorySize64    : 2199290138624
PriorityBoostEnabled       : True
PrivateMemorySize64        : 71127040
PrivilegedProcessorTime    : 00:00:01.7500000
ProcessName                : ApplicationFrameHost
ProcessorAffinity          : 15
Responding                 : True
SessionId                  : 1
StartInfo                  : System.Diagnostics.ProcessStartInfo
StartTime                  : 6/30/2015 3:54:14 PM
SynchronizingObject        :
Threads                    : {7732, 6172, 3132, 7996...}
UserProcessorTime          : 00:00:00.6562500
VirtualMemorySize64        : 2199285780480
EnableRaisingEvents        : False
StandardInput              :
StandardOutput             :
StandardError              :
WorkingSet64               : 38617088
Site                       :
Container                  :


PS C:\> $processes[0].PrivilegedProcessorTime.GetType()    #the property here is of data type TimeSpan.

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     TimeSpan                                 System.ValueType


With Get-Process, the process objects themselves have many properties by default.  One way to make a custom object array is to select a subset from a larger object array.  By far, this is likely what you'll be doing in most of your scripting, so I'm going to break it down for you.


Sorting

As with simple string arrays, you can sort any custom object array in ascending or descending order, however with object arrays, you must specify which property you wish to sort on.

PS C:\> $cdrive | sort LastWriteTime    #sort by the the last time the directory was written to


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        5/23/2015   6:06 AM                PerfLogs
d-r---        6/20/2015   7:05 PM                Users
d-----        6/27/2015   8:20 AM                Windows
d-----        6/30/2015   5:01 PM                HyperV
d-r---         7/1/2015   6:54 PM                Program Files (x86)
d-r---         7/2/2015   5:23 PM                Program Files



PS C:\> $serverstatus | sort Servername -Descending    #sort in reverse order the servername property

ServerName TimeStamp           RDPPort Results
---------- ---------           ------- -------
server5    2015-06-26T09:35:51 3389    Up
server4    2015-06-26T09:35:55 3389    Up
server3    2015-06-26T09:35:54 3389    Up
server2    2015-06-26T09:35:53 3389    Up
server1    2015-06-26T09:35:52 3389    Up



Selecting

By far one of the most useful object array modifiers and you'll send up using it the most of these.  Selecting is a way to remove certain properties from an array, which allows you to do away with the extra columns of data you don't need.  Select is easy to use, just pipe in your object array and list which properties you want output.  We briefly touched on special parameters like -First <#>, -Skip <#>, and -Last <#> which actually work on a record level, not the properties.

PS C:\> $cdrive | select FullName,Attributes,CreationTime,LastAccessTime -Last 3   #Only returns the last 3 entries of the array

FullName                                    Attributes CreationTime         LastAccessTime
--------                                    ---------- ------------         --------------
C:\Program Files (x86) ReadOnly, Directory, Compressed 5/23/2015 4:52:28 AM 7/1/2015 6:54:47 PM
C:\Users               ReadOnly, Directory, Compressed 5/23/2015 4:52:28 AM 6/20/2015 7:05:34 PM
C:\Windows                       Directory, Compressed 5/23/2015 4:52:28 AM 6/27/2015 8:20:52 AM



PS C:\> $processes | Select Name,VirtualMemorySize64,BasePriority,Company -First 4 -Skip 1    

Name       VirtualMemorySize64 BasePriority Company
----       ------------------- ------------ -------
chrome              1354072064            8 Google Inc.
chrome               470994944            8 Google Inc.
chrome               319963136            8 Google Inc.
chrome               474648576            8 Google Inc.




PS C:\> $processes | select * -First 1    #Select * is the wildcard for all properties. This is the same as $processes[0] | Select *


__NounName                 : Process
Name                       : ApplicationFrameHost
Handles                    : 480
VM                         : 2199285780480
WS                         : 38617088
PM                         : 71127040
NPM                        : 29232
Path                       : C:\Windows\system32\ApplicationFrameHost.exe
Company                    : Microsoft Corporation
CPU                        : 2.40625
FileVersion                : 10.0.10130.0 (fbl_impressive.150522-2224)
ProductVersion             : 10.0.10130.0
Description                : Application Frame Host
Product                    : Microsoft® Windows® Operating System
Id                         : 6236
PriorityClass              : Normal
HandleCount                : 480
WorkingSet                 : 38617088
PagedMemorySize            : 71127040
PrivateMemorySize          : 71127040
VirtualMemorySize          : 262524928
TotalProcessorTime         : 00:00:02.4062500
BasePriority               : 8
ExitCode                   :
HasExited                  : False
ExitTime                   :
Handle                     : 5628
SafeHandle                 : Microsoft.Win32.SafeHandles.SafeProcessHandle
MachineName                : .
MainWindowHandle           : 266852
MainWindowTitle            : Calculator
MainModule                 : System.Diagnostics.ProcessModule (ApplicationFrameHost.exe)
MaxWorkingSet              : 1413120
MinWorkingSet              : 204800
Modules                    : {System.Diagnostics.ProcessModule (ApplicationFrameHost.exe),
                             System.Diagnostics.ProcessModule (ntdll.dll), System.Diagnostics.ProcessModule
                             (KERNEL32.DLL), System.Diagnostics.ProcessModule (KERNELBASE.dll)...}
NonpagedSystemMemorySize   : 29232
NonpagedSystemMemorySize64 : 29232
PagedMemorySize64          : 71127040
PagedSystemMemorySize      : 456312
PagedSystemMemorySize64    : 456312
PeakPagedMemorySize        : 72245248
PeakPagedMemorySize64      : 72245248
PeakWorkingSet             : 66281472
PeakWorkingSet64           : 66281472
PeakVirtualMemorySize      : 266883072
PeakVirtualMemorySize64    : 2199290138624
PriorityBoostEnabled       : True
PrivateMemorySize64        : 71127040
PrivilegedProcessorTime    : 00:00:01.7500000
ProcessName                : ApplicationFrameHost
ProcessorAffinity          : 15
Responding                 : True
SessionId                  : 1
StartInfo                  : System.Diagnostics.ProcessStartInfo
StartTime                  : 6/30/2015 3:54:14 PM
SynchronizingObject        :
Threads                    : {7732, 6172, 3132, 7996...}
UserProcessorTime          : 00:00:00.6562500
VirtualMemorySize64        : 2199285780480
EnableRaisingEvents        : False
StandardInput              :
StandardOutput             :
StandardError              :
WorkingSet64               : 38617088
Site                       :
Container                  :




Where Clause

If you are familiar with SQL, you'll recognize the Where-Object (aka "where" or "?" aliases) as a way to exclude certain records given a conditional check to each record.  If a True is produced, the record is included, if a False is calculated, the record is excluded. The parameter for Where-Object is actually a script block which is ran on each record.

PS C:\> $cdrive | Where-Object{$_.mode -like "*r*"}      #returns only records that have the read only attribute set on them (R from mode property).


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-r---         7/2/2015   5:23 PM                Program Files
d-r---         7/1/2015   6:54 PM                Program Files (x86)
d-r---        6/20/2015   7:05 PM                Users



PS C:\> $processes | Where-Object {$_.company -notlike "*microsoft*"}   #returns only records of processes that aren't labeled as Microsoft

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
   4301     146   232976     253580  1291 ...35.02   5072 chrome
    255      38   171652     146496   449 1,789.36   8516 chrome
    253      29    60472      42340   305   239.45   8524 chrome
    285      40   119432     114596   453 1,657.22   8872 chrome
    404      72   284048     523196  1041 ...49.17   8976 chrome
    322      40    91900     101556   349 1,574.55   9076 chrome
    289      37   100332     112892   342    74.33   9628 chrome
    248      30    91844      69756   277   305.22  10400 chrome
    302      37   113524      85084   433   466.67  11176 chrome
    292      40   132888     138396   334    55.25  11636 chrome
    354      14     1308       1512 ...04     2.98    540 csrss
    705      49    13956       6252 ...41   126.80    692 csrss
      0       0        0          4     0               0 Idle
    234      15     2800       3032    69     0.16   8136 LMS
    353      35    26680       7628   592     2.44   6984 MOM
    596      67   123360      80576 ...84 1,876.20   2420 MsMpEng
    269      14     9744       2324 ...06   200.53   3000 NisSrv
    112       8     1172       5684 ...86           10644 SearchFilterHost
    735      25     6720      20576 ...95           12072 SearchProtocolHost
    966      82    94528     103820 33439     7.97   4460 SearchUI
    291      12     4316       4884 ...89    22.70    816 services
     49       3      336        316 ...56     0.08    344 smss
   1647       0      448      63616   129 2,550.19      4 System
     84       8      904       1032 ...94     0.05    684 wininit


Grouping

Grouping actually creates a nested object array (and object array inside of an object array) and is a bit more advanced and less used, so I'm only going to mention it for now. Grouping finds the matching records by the property specified and colapses the records into a new property called Group which is a nested array. Count is also generated to show how many objects are inside of the group property.

PS C:\> $serverstatus | group results         #since all of the results were up, there's only one entry in this group output.

Count Name                      Group
----- ----                      -----
    5 Up                        {@{ServerName=server1; TimeStamp=2015-06-26T09:35:51; RDPPort=3389; Results=Up}, @{Serve...



PS C:\> $processes | group ProcessName 

Count Name                      Group
----- ----                      -----
    1 ApplicationFrameHost      {System.Diagnostics.Process (ApplicationFrameHost)}
    9 chrome                    {System.Diagnostics.Process (chrome), System.Diagnostics.Process (chrome), System.Di...
    3 conhost                   {System.Diagnostics.Process (conhost), System.Diagnostics.Process (conhost), System....
    2 csrss                     {System.Diagnostics.Process (csrss), System.Diagnostics.Process (csrss)}
    1 dllhost                   {System.Diagnostics.Process (dllhost)}
    1 dwm                       {System.Diagnostics.Process (dwm)}
    1 explorer                  {System.Diagnostics.Process (explorer)}
    1 fontdrvhost               {System.Diagnostics.Process (fontdrvhost)}
    1 Idle                      {System.Diagnostics.Process (Idle)}
    1 LMS                       {System.Diagnostics.Process (LMS)}
    1 lsass                     {System.Diagnostics.Process (lsass)}
    1 mmc                       {System.Diagnostics.Process (mmc)}
    1 MOM                       {System.Diagnostics.Process (MOM)}
    1 MsMpEng                   {System.Diagnostics.Process (MsMpEng)}
    1 mstsc                     {System.Diagnostics.Process (mstsc)}
    1 NisSrv                    {System.Diagnostics.Process (NisSrv)}
    2 powershell                {System.Diagnostics.Process (powershell), System.Diagnostics.Process (powershell)}
    1 RuntimeBroker             {System.Diagnostics.Process (RuntimeBroker)}
    1 SearchFilterHost          {System.Diagnostics.Process (SearchFilterHost)}
    1 SearchIndexer             {System.Diagnostics.Process (SearchIndexer)}
    1 SearchProtocolHost        {System.Diagnostics.Process (SearchProtocolHost)}
    1 SearchUI                  {System.Diagnostics.Process (SearchUI)}
    1 services                  {System.Diagnostics.Process (services)}
    1 ShellExperienceHost       {System.Diagnostics.Process (ShellExperienceHost)}
    1 sihost                    {System.Diagnostics.Process (sihost)}
    1 smss                      {System.Diagnostics.Process (smss)}
    1 splwow64                  {System.Diagnostics.Process (splwow64)}
    1 spoolsv                   {System.Diagnostics.Process (spoolsv)}
   12 svchost                   {System.Diagnostics.Process (svchost), System.Diagnostics.Process (svchost), System....
    1 System                    {System.Diagnostics.Process (System)}
    1 taskhostw                 {System.Diagnostics.Process (taskhostw)}
    1 Taskmgr                   {System.Diagnostics.Process (Taskmgr)}
    1 unsecapp                  {System.Diagnostics.Process (unsecapp)}
    1 vmms                      {System.Diagnostics.Process (vmms)}
    1 vmwp                      {System.Diagnostics.Process (vmwp)}
    1 wininit                   {System.Diagnostics.Process (wininit)}
    1 winlogon                  {System.Diagnostics.Process (winlogon)}
    2 WmiPrvSE                  {System.Diagnostics.Process (WmiPrvSE), System.Diagnostics.Process (WmiPrvSE)}
    1 WSHost                    {System.Diagnostics.Process (WSHost)}


Converting

Once you have your array, you might need to transform it into a different format for reporting or saving it.

Converting examples
PS C:\> $serverstatus | ConvertTo-Csv -NoTypeInformation      #converts the object array into a Comma Separated Value (CSV) format

"ServerName","TimeStamp","RDPPort","Results"
"server1","2015-06-26T09:35:51","3389","Up"
"server2","2015-06-26T09:35:52","3389","Up"
"server3","2015-06-26T09:35:53","3389","Up"
"server4","2015-06-26T09:35:54","3389","Up"
"server5","2015-06-26T09:35:55","3389","Up"


PS C:\> $serverstatus | ConvertTo-Html -Title "Server Status"              #HTML - web pages



Server Status

ServerNameTimeStampRDPPortResults
server12015-06-26T09:35:513389Up
server22015-06-26T09:35:523389Up
server32015-06-26T09:35:533389Up
server42015-06-26T09:35:543389Up
server52015-06-26T09:35:553389Up
PS C:\> $serverstatus | ConvertTo-Json #The JSON format is widely used in web API and stores nested object arrays. [ { "ServerName": "server1", "TimeStamp": "2015-06-26T09:35:51", "RDPPort": "3389", "Results": "Up" }, { "ServerName": "server2", "TimeStamp": "2015-06-26T09:35:52", "RDPPort": "3389", "Results": "Up" }, { "ServerName": "server3", "TimeStamp": "2015-06-26T09:35:53", "RDPPort": "3389", "Results": "Up" }, { "ServerName": "server4", "TimeStamp": "2015-06-26T09:35:54", "RDPPort": "3389", "Results": "Up" }, { "ServerName": "server5", "TimeStamp": "2015-06-26T09:35:55", "RDPPort": "3389", "Results": "Up" } ]


Exporting

Exporting is basically the same thing as ConvertTo-____ + save it to a file. So Export-CSV .\filename.csv is the same thing as ConvertTo-CSV | Set-Content .\filename.csv

PS C:\> $serverstatus | Export-Csv myserverstatus.csv -Verbose   #You'll use CSV's a lot as they open up easily in excel

VERBOSE: Performing the operation "Export-Csv" on target "myserverstatus.csv".



PS C:\> $serverstatus | Export-Clixml myserverstatus.xml -Verbose   #Exports to XML format, supports nested object arrays

VERBOSE: Performing the operation "Export-Clixml" on target "myserverstatus.xml".


In Part 3, we'll cover building custom objects from scratch and touch more on hash tables.

Wednesday, July 1, 2015

Quick Script - Find which servers are connecting to your 2003 boxes

With Windows server 2008, we got the lovely Resmon.exe tool which lets you easily see TCP/IP connections, complete with reverse name lookup. This has proven invaluable in troubleshooting issues. But what do you do about your 2003 servers that you are still supportting? Here's a quick script which uses the legacy tool netstat.exe to create a properly formed Powershell table (object array). Remember, UDP is stateless, so it doesn't ever have data for the connection state column.

PS C:\> ((netstat -a | select -skip 4) -replace '^\s+','') -replace '\s+',',' |
>>>     ConvertFrom-Csv -Header ("Protocol","Local Address","Foreign Address","State") |
>>>     sort state -Descending| FT -AutoSize

Protocol Local Address         Foreign Address         State
-------- -------------         ---------------         -----
TCP      127.0.0.1:61840       server10:8080           SYN_SENT
TCP      127.0.0.1:31014       MyPCname:0              LISTENING
TCP      127.0.0.1:63202       server20:443            ESTABLISHED
TCP      192.168.0.2:61819     server30:1433           CLOSE_WAIT
UDP      127.0.0.1:64204       *:*



If you wish to get the processID, just change "netstat -a" to "netstat -ao" and add ,"PID" to the Header line like this. Note: the columns now don't parse correctly for UDP because of how things are being split up. It's possible to fix, but most of the time spent troubleshooting will probably be spent on TCP connections.
PS C:\> ((netstat -ao | select -skip 4) -replace '^\s+','') -replace '\s+',',' |
>>>     ConvertFrom-Csv -Header ("Protocol","Local Address","Foreign Address","State","PID") |
>>>     sort state -Descending| FT -AutoSize

Protocol Local Address         Foreign Address        State        PID
-------- -------------         ---------------        -----        ---
TCP      127.0.0.1:61840       server10:8080          SYN_SENT     1235
TCP      127.0.0.1:31014       MyPCname:0             LISTENING    8765
TCP      127.0.0.1:63202       server20:443           ESTABLISHED  2345
TCP      192.168.0.2:61819     server30:1433          CLOSE_WAIT   3454
UDP      127.0.0.1:64204       *:*                    234

Beginner's Guide - Working with lists (part 1)

One of the great advantages to using Powershell as an administration method in Windows AD environments is its ability to work with many computers or user accounts or files at once.  One of the key concepts is understanding lists.  The basic assumption I make here is that you know how variables are assigned and created.  Lets cover some of the many ways to be effective with lists.

If you'd like to skip ahead:
Part 2: working with object arrays
Part 3: building custom object arrays from scratch
Part 4: manipulating objection arrays and custom selects

Computer Science 101 - Arrays

The first thing you must understand is that there are two types of "lists" in Powershell: hash tables and arrays.  Hash tables are lists of name + value pairs. Notice how they aren't ordered; this is true for all hash tables. Arrays are always ordered.

PS C:\> $HT = @{}        #Declares a new empty hash table
PS C:\> $HT["head"]="hat"
PS C:\> $HT.Add("chest","shirt")
PS C:\> $HT["legs"]="pants"
PS C:\> $HT.Add("feet","shoes")
PS C:\> Write-Output $HT

Name                           Value
----                           -----
chest                          shirt
head                           hat
legs                           pants
feet                           shoes


PS C:\> $HT.GetType()   #This handy method tells you what kind of data your variable is.

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object


We won't spend much time on hash tables as they have very important purposes, but for the direction of this tutorial, we'll be focusing on arrays.  The key takeaway with hash tables is that they have only two "columns" of data, Name and Value.  An array is a collection of objects.  An object in Powershell can be of any data type.  Here's some information on data types in Powershell.

In addition to the basic data types, objects can be any combination of data types.  Think about an excel spreadsheet with many columns of data, each different.  In Powershell, that would be an array of objects, where the row is the object record or item.  The whole spreadsheet has many records.  The columns symbolize the different fields (called properties in Powershell) and each one can be a different type.

For simplicity sake, lets start with an array of strings.  This is probably what you think of when you hear "list".  Notice that they don't have any column headings when you do a Write-Output on them.  Here are a few ways to make lists:

PS C:\> notepad servers.txt         #Enter one server per line
PS C:\> $stringarray = Get-Content .\servers.txt   #Reads in the file and creates a single array, string object per line.

PS C:\> Write-Output $stringarray
Server1
Server2
Server3
Server4
NotAServer


PS C:\> $stringarray.GetType()        #technically, all arrays are just object arrays

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS C:\>


PS C:\> #This is NOT an example of an array, it's simple a multi-line string denoted by the special end markers @"  and  "@
PS C:\> $notAstringArray = @"
>>> lets
>>> try
>>> this
>>> out
>>> "@
PS C:\> $notAstringArray.GetType()    #This is just a string, not an array

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

PS C:\>

PS C:\> $AlsoStringArray = @()           #Definitng an empty array
PS C:\> $AlsoStringArray += "First"      #The += operator basically means append this to my array
PS C:\> $AlsoStringArray += "Second"
PS C:\> $AlsoStringArray += "Third"
PS C:\> Write-Output $AlsoStringArray
First
Second
Third


String Array indexing

So now we have some basic concepts down, lets look a bit more at string arrays specifically.  You can use the built in parameter ".count" which lets you see how many items are in the array.  Also, you can select any record in the array by addressing it's index number.  Arrays are ordered with an index starting at 0 and going up to the .count - 1.

PS C:\> $AnotherStringArray = @("server10","server20","server30")  #another way to make a string array

PS C:\> $AnotherStringArray.count           #Shows number of records in array
3

PS C:\> $AnotherStringArray[0]              #gets the first item in the array
server10

PS C:\> $AnotherStringArray[1]
server20

PS C:\> $AnotherStringArray[2]
server30

PS C:\> $AnotherStringArray[3]              #gets the 4th item, which there is none, so $null is returned (nothing)

PS C:\> $AnotherStringArray[-1]             #special index for getting the last item
server30

PS C:\> $AnotherStringArray[-2]             #gets the second to last item
server20

PS C:\> $AnotherStringArray[0].GetType()    #If we do a .GetType() on the actual record, it's a data type of string, not array

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object


PS C:\>



More String Array fun

Once you have your String Array, you can do fun things like selecting parts of it, and sorting it alphabetically (or reverse).  You can also select records which contain (or don't contain) certain characters, are of a minimum (or maximum) length, and of course write it out to a file.

PS C:\> $servers = Get-Content C:\users\bryan.VINES\servers.txt

PS C:\> $servers | select -first 3 -skip 1    #returns the first 3 items in the list after skipping 1
Server2
Server3
Server4

PS C:\> $servers | select -Last 2             #returns the last 2 items
Server4
NotAServer

PS C:\> $servers | sort -Descending
Server4
Server3
Server2
Server1
NotAServer

PS C:\> $servers | Where-Object{$_ -like "server*"}  #returns only items that start with server (non case sensitive)
Server1
Server2
Server3
Server4

PS C:\> $servers | Where-Object{$_ -like "*S*2*"}   #returns the items that have the letter S and then 2 in order in the them
Server2

PS C:\> $servers | Set-Content C:\outservers.txt -Verbose     #writes the string array to a text file.
VERBOSE: Performing the operation "Set Content" on target "Path: C:\outservers.txt".


Well, that's all for now.  In part 2, we'll look at other object arrays.

Monday, June 29, 2015

Powershell Scripts - Get-Parse & Publish-Parse - upload and download data from Parse.com Core DB

If you haven't checked out Parse as a hosted database platform for your website and/or app, you should.  They are free to start out and when your site or app takes off, they scale up with your traffic.  It's great for startups.  I didn't see a connector in Powershell to upload/download data from the core DB, so I made a pair of functions to do just that. They require Invoke-RestMethod which requires Powershell version 3 or later.

Lets try to upload a very simple table of 10 items, 2 columns:

PS C:\> $Data | FT -AutoSize

   code available
   ---- ---------
4993918      True
5449831      True
9565284      True
9251223      True
6062778      True
5107628      True
3599651      True
6477287      True
7101669      True
8115872      True


PS C:\> Publish-Parse -Class 'Codes' -AppID $AppID -APIKey $APIKey -Data $Data

createdAt                objectId  
---------                --------  
2015-06-29T23:42:14.104Z pmgrk5M87c
2015-06-29T23:42:14.330Z ivWv0Va6vE
2015-06-29T23:42:14.552Z Srj9Rcsvek
2015-06-29T23:42:14.766Z 4Muyx5v3kc
2015-06-29T23:42:14.978Z 6o4UrWc12J
2015-06-29T23:42:15.209Z 4EKTi7ZkTl
2015-06-29T23:42:15.423Z 6LMOBaSlbw
2015-06-29T23:42:15.646Z VzEgWXYbCV
2015-06-29T23:42:15.858Z YRIyxUD3Ue
2015-06-29T23:42:16.080Z UJgd2vfP5Y


I can see the new table in my web data browser in Parse:





Lets try to get that data back. Notice how Parse adds extra fields; if you don't want this in your data, use Select -ExcludeProperty to remove them.

PS C:\> Get-Parse -Class 'Codes' -AppID $AppID -APIKey $APIKey  | FT -AutoSize

available    code createdAt                objectId   updatedAt               
---------    ---- ---------                --------   ---------               
     True 4993918 2015-06-29T23:42:14.104Z pmgrk5M87c 2015-06-29T23:42:14.104Z
     True 5449831 2015-06-29T23:42:14.330Z ivWv0Va6vE 2015-06-29T23:42:14.330Z
     True 9565284 2015-06-29T23:42:14.552Z Srj9Rcsvek 2015-06-29T23:42:14.552Z
     True 9251223 2015-06-29T23:42:14.766Z 4Muyx5v3kc 2015-06-29T23:42:14.766Z
     True 6062778 2015-06-29T23:42:14.978Z 6o4UrWc12J 2015-06-29T23:42:14.978Z
     True 5107628 2015-06-29T23:42:15.209Z 4EKTi7ZkTl 2015-06-29T23:42:15.209Z
     True 3599651 2015-06-29T23:42:15.423Z 6LMOBaSlbw 2015-06-29T23:42:15.423Z
     True 6477287 2015-06-29T23:42:15.646Z VzEgWXYbCV 2015-06-29T23:42:15.646Z
     True 7101669 2015-06-29T23:42:15.858Z YRIyxUD3Ue 2015-06-29T23:42:15.858Z
     True 8115872 2015-06-29T23:42:16.080Z UJgd2vfP5Y 2015-06-29T23:42:16.080Z


PS C:\> Get-Parse -Class 'Codes' -AppID $AppID -APIKey $APIKey  -objectID "pmgrk5M87c","VzEgWXYbCV" | FT -AutoSize

available    code createdAt                objectId   updatedAt               
---------    ---- ---------                --------   ---------               
     True 4993918 2015-06-29T23:42:14.104Z pmgrk5M87c 2015-06-29T23:42:14.104Z
     True 6477287 2015-06-29T23:42:15.646Z VzEgWXYbCV 2015-06-29T23:42:15.646Z



Source Code:

Sunday, June 28, 2015

Powershell Script - Compare-SourceFiles - source code comparison between environments (prod, bcp, qa, dev)

I just built this little script this weekend while troubleshooting a release. We needed a way to compare the code between different environments as well as compare folder permission security within environments on different nodes in the farm. This function does exactly that, producing a nice report (use -ReportFile to output to a file as well) used to prevent change drift and environment variance. If the files are text files, a line by line comparison is also generated should the file hashes be different, which shows specific lines that are different. Binary files are still flagged as different should their MD5 hashes be different. When checking ACLs, effective file permissions are displayed for each source which can speed up troubleshooting permissions issues.


PS c:\> Compare-SourceFiles -Source1 \\server1\c$\prod -Source2 \\server2\c$\dev -ACLs

WARNING: Files missing from '\\server1\c$\prod': 3
\FrameXML.log
\gx.log
\Sound.log



WARNING: File Hash different: '\a.exe'


WARNING: ACL Access Permissions different: '\a.exe'
   File Permissions: '\\server1\c$\prod\a.exe'

FileSystemRights AccessControlType IdentityReference      IsInherited InheritanceFlags PropagationFlags
---------------- ----------------- -----------------      ----------- ---------------- ----------------
     FullControl             Allow BUILTIN\Administrators       False             None             None
     FullControl             Allow NT AUTHORITY\SYSTEM          False             None             None
     FullControl             Allow DOMAIN\User1                 False             None             None



   File Permissions: '\\server2\c$\dev\a.exe'

           FileSystemRights AccessControlType IdentityReference                IsInherited InheritanceFlags PropagationFlags
           ---------------- ----------------- -----------------                ----------- ---------------- ----------------
                FullControl             Allow BUILTIN\Administrators                 False             None             None
ReadAndExecute, Synchronize             Allow Everyone                               False             None             None
        Modify, Synchronize             Allow NT AUTHORITY\Authenticated Users       False             None             None
                FullControl             Allow NT AUTHORITY\SYSTEM                    False             None             None





WARNING: File Hash different: '\connection.log'

InputObject                                                                                    SideIndicator
-----------                                                                                    -------------
2/16 16:02:39.744  GRUNT: state: LOGIN_STATE_CONNECTING result: LOGIN_OK                       =>                  
2/16 16:02:39.946  GRUNT: state: LOGIN_STATE_AUTHENTICATING result: LOGIN_OK                   =>           
2/16 16:02:40.128  GRUNT: state: LOGIN_STATE_CHECKINGVERSIONS result: LOGIN_OK                 =>           
2/16 16:02:40.205  GRUNT: state: LOGIN_STATE_HANDSHAKING result: LOGIN_OK                      =>           
2/16 16:02:40.375  GRUNT: state: LOGIN_STATE_AUTHENTICATED result: LOGIN_OK                    =>           
2/16 16:02:40.375  ClientConnection Initiating: COP_CONNECT code=CSTATUS_CONNECTING            =>           
2/16 16:02:40.708  ClientConnection Completed: COP_CONNECT code=RESPONSE_CONNECTED result=TRUE =>           
2/16 16:02:40.724  ClientConnection Initiating: COP_AUTHENTICATE code=CSTATUS_AUTHENTICATING   =>           
2/16 16:02:41.311  ClientConnection Completed: COP_AUTHENTICATE code=AUTH_OK result=TRUE       =>           
2/16 16:02:41.780  ClientConnection Initiating: COP_GET_CHARACTERS code=43                     =>           
2/16 16:02:42.286  ClientConnection Completed: COP_GET_CHARACTERS code=44 result=TRUE          =>           
2/16 16:02:59.661  GRUNT: state: LOGIN_STATE_DISCONNECTED result: LOGIN_OK                     =>           
2/16 16:03:03.816  ClientConnection Initiating: COP_LOGIN_CHARACTER code=77                    =>           
2/16 16:03:04.237  ClientConnection Completed: COP_LOGIN_CHARACTER code=78 result=TRUE         =>           
2/16 16:01:53.409  GRUNT: state: LOGIN_STATE_CONNECTING result: LOGIN_OK                       <=           
2/16 16:02:14.449  GRUNT: state: LOGIN_STATE_FAILED result: LOGIN_CONVERSION_REQUIRED          <=           
2/16 16:02:14.453  GRUNT: state: LOGIN_STATE_FAILED result: LOGIN_CONVERSION_REQUIRED          <=           





WARNING: File Hash different: '\cpu.log'

InputObject                                                                         SideIndicator
-----------                                                                         -------------
2/16 16:02:30.896  vendor: 1                                                        =>           
2/16 16:02:30.896  features: 00000397                                               =>           
2/16 16:02:30.896  cores: 2                                                         =>           
2/16 16:02:30.896  threads: 4                                                       =>           
2/16 16:02:30.896  vendor id string= GenuineIntel                                   =>           
2/16 16:02:30.896  standard (13): 1b=02100800 1c=7FDAFBBF 1d=bfebfbff 4a=1C004121   =>           
2/16 16:02:30.896  extended (8): 1c=00000021 1d=2c100000 8c=00000000                =>           
2/16 16:02:30.896  processor brand string= Intel(R) Core(TM) i5-4670K CPU @ 3.40GHz =>           
2/16 16:01:42.716  vendor: 1                                                        <=           
2/16 16:01:42.716  features: 00000397                                               <=           
2/16 16:01:42.716  cores: 2                                                         <=           
2/16 16:01:42.716  threads: 4                                                       <=           
2/16 16:01:42.716  vendor id string= GenuineIntel                                   <=           
2/16 16:01:42.716  standard (13): 1b=00100800 1c=7FDAFBBF 1d=bfebfbff 4a=1C004121   <=           
2/16 16:01:42.716  extended (8): 1c=00000021 1d=2c100000 8c=00000000                <=           
2/16 16:01:42.716  processor brand string= Intel(R) Core(TM) i5-4670K CPU @ 3.40GHz <=           






==============================
File Source Comparison Summary
==============================

Path              FileCount MissingFiles SharedFiles DifferentHashes DifferentACLs
----              --------- ------------ ----------- --------------- -------------
\\server1\c$\prod         3            3           3               3             1
\\server2\c$\dev          6            0           3               3             1



Saturday, June 27, 2015

How-To - Name your functions correctly

When you name functions, there is a set standard which you must follow or you'll see this error if you try and put your function into a module and load it:

WARNING: Some imported command names include unapproved verbs which might make them less discoverable. Use the Verbose parameter for more detail or type Get-Verb to see the list of approved verbs.

That format is the basic combination of a Verb and a Noun joined by a dash ("-").  The Noun can be an alphanumeric string, no spaces and a limited scope of special characters (underscore is ok).  The Verb however, has to be on a specific list.  Lets take a look at the valid list:

PS C:\> Get-Verb | Format-Wide -AutoSize


Add          Clear        Close        Copy         Enter        Exit        Find        Format     
Get          Hide         Join         Lock         Move         New         Open        Optimize   
Pop          Push         Redo         Remove       Rename       Reset       Resize      Search     
Select       Set          Show         Skip         Split        Step        Switch      Undo       
Unlock       Watch        Backup       Checkpoint   Compare      Compress    Convert     ConvertFrom
ConvertTo    Dismount     Edit         Expand       Export       Group       Import      Initialize 
Limit        Merge        Mount        Out          Publish      Restore     Save        Sync       
Unpublish    Update       Approve      Assert       Complete     Confirm     Deny        Disable    
Enable       Install      Invoke       Register     Request      Restart     Resume      Start      
Stop         Submit       Suspend      Uninstall    Unregister   Wait        Debug       Measure    
Ping         Repair       Resolve      Test         Trace        Connect     Disconnect  Read       
Receive      Send         Write        Block        Grant        Protect     Revoke      Unblock    
Unprotect    Use

If you run Get-Verb by itself, you'll get a list that runs off the page, but also shows which group the verbs belong to.  For the most part, try to use the verb which mostly relates to the action that you are trying to do.  You can see what other functions do by simply looking at same verb function's help:

PS C:\> Get-Help wait* | Format-Table Name,Synopsis -AutoSize

Name          Synopsis                                                                              
----          --------                                                                              
Wait-Job      Suppresses the command prompt until one or all of the Windows PowerShell background...
Wait-Debugger Stops a script in the debugger before running the next statement in the script.       
Wait-Event    Waits until a particular event is raised before continuing to run.                    
Wait-Process  Waits for the processes to be stopped before accepting more input.                    

To see verbs from all commands that are currently loaded, try this little block.  I've marked it up the code to help explain what each line does.  Try running the first line (leaving off the pipe "|"), then the first and second lines, then the first 3 lines, etc.  You can see how passing the output of one cmdlet into the next works this way.


Get-Command |                      #Gets all commands from all loaded modules 
select -ExpandProperty Name |      #Expands the Name column
Where-Object{$_.contains("-")} |   #Only selects names containing the dash character 
ForEach-Object{$_.split("-")[0]} | #For each of the names, split it up into 2 objects on the dash, select only the 1st
select -Unique |                   #Remove duplicates
sort |                             #sort alphabetically
ConvertFrom-Csv -Header "Name" |   #Converts a string list into an object list
Format-Wide -AutoSize              #Displays the list in a multi-column list


Lastly, if you are planning on releasing a module with a bunch of functions, try naming them in a similar way to indicate they are all from your module.  Most third party modules do this.  The format should be Verb-PrefixNoun.  For example, the NetApp DataONTAP Powershell module uses the prefix "Na", so there commands look like this:

  • Get-NaLun
  • Set-NaVol
  • Restore-NaHostFile

Friday, June 26, 2015

Quick Script - Test-PingRDP - Check if servers are pingable and have RDP enabled

I answered a question on a forum with a similar version of this script.  I made it a function instead of a lose script, which have some negatives comparatively.  Functions also are much easier to wrap up into a module for easy distribution.  PSGet/OneGet also requires everything in modules.

Example:
PS C:\> @"
Server1
Server2
Server3
Server4
NotAServer
"@ | Set-Content servers.txt

PS C:\>Test-IPRDP -ServerList servers.txt

PS C:\>Import-Csv .\ServerStatus.csv

ServerName TimeStamp           RDPPort Results
---------- ---------           ------- -------
server1    2015-06-26T09:35:51 3389    Up     
server2    2015-06-26T09:35:52 3389    Up     
server3    2015-06-26T09:35:53 3389    Up     
server4    2015-06-26T09:35:54 3389    Up     
NotAServer 2015-06-26T09:35:55 3389    Down     

PS C:\>


Code:

Beginner's Guide - Powershell for the newbie

Whether you're new to the world of Windows administration or you've been living under a rock for the past 8 years, it's OK not to know about perhaps the single most effective tool for administrating Windows environments since Active Directory.  I'm not going to reinvent the wheel in this blog entry as there are tons of great resources out there, but I am going to provide some of my favorites and recommendations.

Above all, in order to learn how to use Powershell, you must practice.  Most commands are innocuous and safe to run on your workstation or laptop; but to be safe, you are better off having a dedicated PC or virtual machine to create a sandbox environment.  I'll be creating a blog called "How-to: Building a Virtual Lab" soon and I'll add the link when it's up.

For now, lets get to the good stuff.

Getting Started


Building the knowledge


Coming to Windows?
  • Powershell for Unix people - Many unix commands work in a similar way to bash, though not case sensitive thank goodness.
  • Powershell on Mac - well editing on a mac anyways.  Powershell over SSH will come soon enough and then you'll be able to run commands on a remote server from your non-windows client.


Books


Classroom Learning
  • Interface Powershell Training -  I took the Don Jones Master Class (boot camp) last year and he blew my mind in 5 days, despite my 6 previous years of Powershell experience. Worth the $3500 and they offer web based learning.  If you can get work reimbursement, all the better.
  • Powershell.org Events - Many free webinars and if you are lucky, you might have a Powershell meetup group near you.


That's all for now. Did I miss something that you think should be on this list? Let me know in a comment below or Tweet me.

Thursday, June 25, 2015

Powershell Script - Get-LastBootTime - remotely find out when your servers where last rebooted

Here's another handy and simple function which lets you query a bunch of servers at once to find out when they were last rebooted.  It's taking advantage of Get-WmiObject and the root\civ2\Win32_OperatingSystem class.  For maximum parallelization, I've forced pipeline usage to first colapse the process block into a single array to create only one Get-WmiObject call.  This is significantly faster than the default pipeline behavior which is sequential, with 1000 servers completing under a minute (local LAN) vs sequential calls taking close to 10 minutes.

Here's it in action:

PS C:\> Get-Content .\serverlist.txt | Get-LastBootTime

Name      UpSince
----      -------
NOTSERVER Unknown             
SERVER001 6/20/2015 10:46:25 PM
SERVER002 6/20/2015 10:26:24 PM
SERVER003 6/20/2015 10:30:17 PM
SERVER004 6/20/2015 10:27:52 PM
SERVER005 6/20/2015 10:38:27 PM
SERVER006 6/20/2015 10:30:12 PM
SERVER007 6/20/2015 11:54:01 PM
SERVER008 6/20/2015 10:28:39 PM
SERVER009 6/20/2015 10:26:15 PM
SERVER010 6/20/2015 10:27:01 PM

Source Code:

Wednesday, June 24, 2015

Powershell Script - ConvertTo-HtmlTable - making pretty html formatted tables for reports and emails.

Here's a nifty utility function I built long ago that really makes emailing reports or rendering web reports easy as pie (cake is a lie). You can even change the font color, background color, border color, border width, and cell padding around the text.


Here's a little example which displays the current directory contents and opens up your browser for you after the report's finished.

dir . | select Name,Length,Mode,LastWritetime | ConvertTo-HtmlTable | Set-Content .\test.html
Invoke-Item .\test.html

And the output in your browser:

Tuesday, June 23, 2015

Powershell Script - Invoke-Multithread - Make any script multithreaded

Here's a very useful wrapper which helps you turn your single threaded sequential functions into multithreaded beasts which complete in a fraction of the time when addressing many servers (or whatever network devices).

The function works by taking your code as a [scriptblock] object and a list of computers (servers, IPs, whatever) and breaking up the list based on a throttle limit (default is 25) and starting powershell jobs.  The script waits for each job to finish and captures the return from the job and parses it into an object array and associates each computer's output with the name.

This function is great for generating reports, pushing configurations, making changes, etc, where native multithreaded cmdlets won't work and sequential code must be used.  Most admins new to powershell don't know how to leverage built in commands that automatically thread and this wrapper function does a nice job at speeding up slow scripts.


Here's an example where I'm just checking to see if the server is a windows server, but this could be any function which takes a string array "ComputerName" as a parameter.

And the output:

ComputerName  Output
------------  ------
SERVER1       Is a Windows Server
SERVER2       Is a Windows Server
SERVER3       Is a Windows Server
SERVER4       Is a Windows Server
SERVER5       Is a Windows Server
SERVER6       Is a Windows Server
SERVER7       Is a Windows Server
SERVER8       Is a Windows Server
SERVER9       Is a Windows Server
SERVER10      Is a Windows Server
SERVER11      Is a Windows Server
SERVER12      Is a Windows Server
SERVER13      Is a Windows Server
SERVER14      Is a Windows Server
SERVER15      Is a Windows Server
SERVER16      Is a Windows Server
SERVER17      Is a Windows Server
SERVER18      Is a Windows Server
SERVER19      Is a Windows Server
SERVER20      Is a Windows Server
SERVER21      Is a Windows Server
SERVER22      Is a Windows Server
SERVER23      Is a Windows Server
SERVER24      Is a Windows Server
SERVER25      Is a Windows Server
SERVER26      Is a Windows Server
SERVER27      Is a Windows Server
SERVER28      Is a Windows Server
SERVER29      Is a Windows Server
SERVER30      Is a Windows Server
SERVER31      Is a Windows Server
SERVER32      Is a Windows Server
SERVER33      Is a Windows Server
SERVER34      Is a Windows Server
SERVER35      Is a Windows Server
SERVER36      Is a Windows Server
SERVER37      Is a Windows Server
SERVER38      Is a Windows Server
SERVER39      Is a Windows Server
SERVER40      Is a Windows Server
SERVER41      Is a Windows Server
SERVER42      Is a Windows Server
SERVER43      Is a Windows Server
SERVER44      Is a Windows Server
SERVER45      Is a Windows Server
SERVER46      Is a Windows Server
SERVER47      Is a Windows Server
SERVER48      Is a Windows Server
SERVER49      Is a Windows Server
SERVER50      Is a Windows Server
NAS1          Is NOT a Windows Server

Monday, June 22, 2015

Powershell script: Cleaning up C:\Windows\Installer directory - the correct way

This very simple script is built off Heath Stewart's VB Script which identifies the files linked for installed products that need to be kept.  My powershell wrapper simply takes his output and automates the deleting of what's not needed.  Usage is simple: drop the script in a temp directory, and run. It will create and run Heath's script which creates another file (output.txt) which is then read back in and parsed.  You'll see what's being removed and what's being kept.

It can be pushed as a scriptblock to remote servers using PS Remoting or PSexec.exe.

Enjoy!

Hello World!

Hello there fellow techies, scripters, IT professionals, developers.  I'm Bryan, Systems Architect and automation guru specializing in Powershell with experience with most of Microsoft technologies (see my Linkedin profile for more details).

The purpose of this blog is to pay back the 15+ years of professional experience I've acquired as much of my knowledge was built on blogs like these.  With Microsoft's shift towards an open source model, I thought it'd be a great time to open up my library of over 2000 scripts.  I'll be documenting how-to's as I build out new environments and labs as well as sharing some of my scripts and modules.

The usual disclaimer: I'm not responsible for anything you break by running my scripts or following my how-to's.  Please use caution and take time to understand what you are doing before you run something (I'll link technet as much as possible).  Always test in a non-production environment if possible.  My code is free to use, but please give credit.  Materials on this blog or any of my code repositories may not be republished without explicit written authorization from me.