PowerShell for Windows Admins


April 24, 2012  2:01 PM

UK PowerShell Group–April 2012 slides and recording

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

The slides, demo script and recording are available from

https://skydrive.live.com/?cid=43cfa46a74cf3e96#cid=43CFA46A74CF3E96&id=43CFA46A74CF3E96%212957

Thank you again to everyone who attended the Live Meeting this evening

April 23, 2012  2:56 PM

Reminder–UK User group 24 April

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Quick reminder that 24 April sees the first of 2 sessions on new PowerShell functionality in Windows 2012 – Windows Server 8

Details from

http://msmvps.com/blogs/richardsiddaway/archive/2012/04/13/uk-powershell-group-24-april-2012.aspx


April 21, 2012  4:56 AM

Scripting Games 2012 comments: #10 Advanced event 5

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/06/2012-scripting-games-advanced-event-5-list-errors.aspx

This is the one I was asked to supply a commentary for

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/20/expert-commentary-2012-scripting-games-advanced-event-5.aspx

 

This one has raised an interesting interpretation issues.  The last design point asks:

  • Your output should be organized such that the largest source of errors appears at the top of the output.

The figure shows the data sorted by Event source  

 

I read the design point as log with the largest number of errors comes first.  The script I produced for the commentary doesn’t meet that last design point so I’ve revised it

#Requires -Version 2            
function Get-EventEntryCount{             
[CmdletBinding()]             
param (             
 [parameter(Position=0,            
   ValueFromPipeline=$true,             
   ValueFromPipelineByPropertyName=$true)]            
 [Alias("CN", "Computer")]              
 [string[]]$computername="$env:COMPUTERNAME",            
            
 [parameter(Position=1)]            
 [ValidateSet("Error", "Information", "FailureAudit", "SuccessAudit", "Warning", "All", "*")]            
 [string]$eventtype="Error"             
)             
BEGIN{            
  $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()            
  $testadmin = `
  (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole(`
  [Security.Principal.WindowsBuiltinRole]::Administrator)            
              
  if (!$testadmin){            
   Throw "Must be run with elevated privileges"            
  }            
}#begin             
PROCESS{            
  foreach ($computer in $computername ){            
    switch ($computer) {            
     "."         {$computer="$env:COMPUTERNAME"}            
     "localhost" {$computer="$env:COMPUTERNAME"}            
    }            
            
    Write-Verbose "Processing computer: $computer"            
                
    $data = @()            
    if (Test-Connection -ComputerName $computer -Count 1 -Quiet){            
            
    Write-Verbose "Starting Remote Registry service on $computer"            
    $origrrsrv = Get-WmiObject -Class Win32_Service -Filter "Name='RemoteRegistry'" `
    -ComputerName $computer            
            
    if ($origrrsrv.StartMode -eq "Disabled") {            
      Set-Service -Name RemoteRegistry -ComputerName $computer -StartupType "Manual"            
    }            
            
    if ($origrrsrv.State -ne "Running") {            
      $origrrsrv.StartService() | Out-Null            
    }            
            
    Write-Verbose "Retrieving logs for $computer"            
                  
    Get-EventLog -List -ComputerName $computer |            
    foreach {            
      $log = $_.Log            
      Write-Verbose "Processing log: $log"            
                    
      if ($_.Entries.Count -gt 0) {            
         Write-Debug "Processing event type $eventtype"            
                      
         $n = Get-EventLog -LogName $($_.Log) -EntryType $eventtype -ComputerName $computer `
         -ErrorAction SilentlyContinue            
                      
         if ($n -ne $null){            
           Write-Debug "Entries found"            
           $n | group Source -NoElement |             
           foreach{            
                       
              $data += New-Object -TypeName PSObject -Property @{            
                 ComputerName = $computer            
                 LogName = $log            
                 EntryType = $eventtype            
                 EntrySource = $($_.Name)            
                 EntryCount = $($_.Count)            
             }            
           }            
         }            
       }  # end if entries            
       else {            
         Write-Verbose "$($computer): $log is empty"            
       }            
                    
            
        if ($origrrsrv.State -eq "Stopped") {            
          $origrrsrv.StopService() | Out-Null            
        }            
            
        if ($origrrsrv.StartMode -eq "Disabled") {            
          Set-Service -Name RemoteRegistry -ComputerName $computer -StartupType "Disabled"            
        }            
            
     } # end of log processing foreach            
   }            
   else {            
     Write-Warning "Cannot contact $computer"            
   } # end if ping            
   Write-Output $data             
 } ## end computer foreach            
}#process             
END{}#end            
            
<# 
.SYNOPSIS
Counts the number of entries of a given type
in the event logs of a system

.DESCRIPTION
One or more computers - from pipeline or parameter -  are
accessed to read the envent logs and count the entries of a 
given type. Empty logs are tested and the count is set to zero

.PARAMETER  computername
Name of computer for which log information
is to be retrieved

.PARAMETER  eventtype
Log entry type to count.
Accepted values are - 
"Error", "Information", "FailureAudit", 
"SuccessAudit", "Warning", "All", "*"

.EXAMPLE
Get-EventEntryCount 

Accesses logs on local machine. Peforms default display

.EXAMPLE
Get-EventEntryCount -computername "." | 
sort LogName, EntryCount -Descending | 
Format-Table EntrySource, EntryCount -GroupBy LogName

Accesses logs on local machine. Format display and group by logname

.EXAMPLE
"dc02", "webr201", "server02" | 
Get-EventEntryCount | 
sort Computer, LogName, EntryCount -Descending | 
Format-Table Logname, EntrySource, EntryCount -GroupBy Computer

Accesses logs on remote machines. Computer names accepted from pipeline.
Format display and group by computer

.EXAMPLE
Get-EventEntryCount -computername "dc02", "webr201", "server02" |
sort Computer, LogName, EntryCount -Descending | 
Format-Table Logname, EntrySource, EntryCount -GroupBy Computer

Accesses logs on remote machines. Computer names accepted as array.
Format display and group by computer

.INPUTS
Computer name - string or string array
Envent type - string. Must be member of set

.OUTPUTS
Returns a custom object with
properties:
ComputerName - name of computer
LogName - name of log
EntryType - Type of log entry
EntryCount - count of entries
EntrySource - event source

.NOTES


.LINK

#>            
            
}


April 17, 2012  12:45 PM

Scripting Games 2012 comments: #9 Beginners event 5

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Beginners event 5

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/06/2012-scripting-games-beginner-event-5-provide-a-source-and-errors.aspx

Looking for problem applications. Need report from each server form application log listing source and number of errors.

Requirements:

  • should be capable of running against remote machine
  • doesn’t need to run remotely for this scenario
  • don’t need to check for admin rights
  • assume have permissions to query application log
  • use standard PowerShell cmdlets
  • solution should be simple and straightforward
  • don’t need to write to text file BUT should should be easily redirectable when needed
  • don’t need comment based help
  • extra points for sorting so source with greatest number of errors at top of list

Reading the event log using standard PowerShell cmdlets -  what do we have available?

Get-Command *event* -CommandType cmdlet

shows a bunch of cmdlets. Lets narrow it down

Get-Command *eventlog* -CommandType cmdlet

shows that Get-EventLog is a good choice.

NOTE: Get-WinEvent is also available but the syntax can be more convoluted. As we are reading a classic event log Get-Eventlog is the simplest solution.

What parameters do we have available?

PS> Get-Help Get-EventLog

NAME
    Get-EventLog

SYNOPSIS
    Gets the events in an event log, or a list of the event logs, on the local or remote computers.

SYNTAX 
 

Get-EventLog [-AsString] [-ComputerName <string[]>] [-List] [<CommonParameters>]

Get-EventLog [-LogName] <string> [[-InstanceId] <Int64[]>] [-After <DateTime>] [-AsBaseObject]
[-Before <DateTime>] [-ComputerName <string[]>] [-EntryType <string[]>] [-Index <Int32[]>]
[-Message <string>] [-Newest <int>] [-Source<string[]>] [-UserName <string[]>] [<CommonParameters>]

Look at the synopsis – local or remote computers. Computername for local/remote machines. EntryType gives the type of entry. Thats about it.

PS> Get-Help Get-EventLog -Parameter EntryType

-EntryType <string[]>
    Gets only events with the specified entry type. Valid values are Error, Information, FailureAudit, SuccessAudit, and Warning. The default is all events.

    Required?                    false
    Position?                    named
    Default value                All events
    Accept pipeline input?       false
    Accept wildcard characters?  false

What does an entry look like?

PS> Get-EventLog -LogName Application | select -f 1 | Format-List

Index              : 83084
EntryType          : Information
InstanceId         : 1073872902
Message            : BBSvc has stopped.
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {BBSvc}
Source             : BBSvc
TimeGenerated      : 17/04/2012 18:00:17
TimeWritten        : 17/04/2012 18:00:17
UserName           :

 

We have an entry type so we can select on that and we have a source.  How can we can the total number of entries per source?

 

PS> Get-EventLog -LogName Application -EntryType Error | group Source

Count Name                      Group
—– —-                      —–
  166 SideBySide                {System.Diagnostics.EventLogEntry, System.Di
   27 Application Error         {System.Diagnostics.EventLogEntry, System.Di
    1 System Restore            {System.Diagnostics.EventLogEntry}
    2 Microsoft-Windows-Rest… {System.Diagnostics.EventLogEntry, System.Di
    3 VSS                       {System.Diagnostics.EventLogEntry, System.Di
   20 Application Hang          {System.Diagnostics.EventLogEntry, System.Di
    1 MsiInstaller              {System.Diagnostics.EventLogEntry}

 

All we need is the Count and the name so we can use the –NoElement parameter

PS> Get-EventLog -LogName Application -EntryType Error | group Source -NoElement

Count Name
—– —-
  166 SideBySide
   27 Application Error
    1 System Restore
    2 Microsoft-Windows-Rest…
    3 VSS
   20 Application Hang
    1 MsiInstaller

 

But  we want them in descending order – actually requirements state largest number of errors at the top of the list which we already have but we will show willing and actually sort the list Smile

PS> Get-EventLog -LogName Application -EntryType Error |
group Source -NoElement | sort Count -Descending

Count Name
—– —-
  166 SideBySide
   27 Application Error
   20 Application Hang
    3 VSS
    2 Microsoft-Windows-Rest…
    1 MsiInstaller
    1 System Restore

 

If you want to see the whole name

PS> Get-EventLog -LogName Application -EntryType Error | group Source -NoElement |
sort Count -Descending | Format-table Count, Name -AutoSize

Count Name
—– —-
  166 SideBySide
   27 Application Error
   20 Application Hang
    3 VSS
    2 Microsoft-Windows-RestartManager
    1 MsiInstaller
    1 System Restore

 

We haven’t shown how to access a remote machine

PS> Get-EventLog -LogName Application -EntryType Error -Computername $env:COMPUTERNAME |
group Source -NoElement | sort Count -Descending | Format-table Count, Name –AutoSize

saving to a file:

PS> Get-EventLog -LogName Application -EntryType Error -Computername $env:COMPUTERNAME |
group Source -NoElement | sort Count -Descending | Format-table Count, Name -AutoSize |
Out-File beg5.txt

is legitimate and gives you exactly what you see on screen – though object police will scream at you for using format-table so if you want to conform try

PS> Get-EventLog -LogName Application -EntryType Error -Computername $env:COMPUTERNAME |
group Source -NoElement | sort Count -Descending | Out-File beg5.txt

but long source names may be truncated

if you really want to demonstrate you know what you are doing

PS> Get-EventLog -LogName Application -EntryType Error -Computername $env:COMPUTERNAME |
group Source -NoElement | sort Count -Descending | Tee-Object -FilePath beg5.txt

tee-object writes to a file and passes along pipeline to finally display

don’t use Add-Content or Set-Content as as all you will get is a set of lines like this

Microsoft.PowerShell.Commands.GroupInfoNoElement

These cmdlets don’t resolve the objects to the data

The Get-WinEvent syntax would be

Get-WinEvent -ComputerName $env:COMPUTERNAME -FilterHashtable @{‘LogName’=’Application’; ‘Level’=2}

which to my mind isn’t as simple as Get-EventLog.

And finally if you just can’t live without aliases – get-eventlog doesn’t have one.


April 15, 2012  10:17 AM

PowerShell and WMI

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

My latest book – PowerShell and WMI – has gone to the printers. Expect it soon at a book shop near you.

www.manning.com/powershellandwmi

for more details


April 15, 2012  3:04 AM

Manning PowerShell book offer

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Manning are offering 50% off PowerShell in Practice using code dotd0415cc in the promotional code box at www.manning.com

 

The same code gets you 50% off PowerShell and WMI AND Advanced PowerShell

 

This is FOR TODAY ONLY


April 14, 2012  9:34 AM

Scripting Games 2012 comments: #8 Beginners Event 4

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/05/2012-scripting-games-beginner-event-4-compare-two-folders.aspx

Makes sure you run the script provided with the event to get the right folders. The script randomly removes 1 file from each folder – sneaky.

After last nights head scratching this one is much simpler. It’s solvable with one line of PowerShell.

For this one we have a data folder and a back up folder we need to compare. 

Requirements:

  • Content of backup folder is same as data folder
  • Only compare file names
  • Show files in one folder that don’t exist in other
  • Return file information objects as result of comparison
  • Don’t need complicated script
  • Unnecessary complexity costs points
  • Extra points for native PowerShell cmdlets and code simplicity

We need to deal with file names – so that implies Get-ChildItem.  We can see the contents of the files:

Get-ChildItem -Path c:\1
Get-ChildItem -Path c:\2

Do we have anything to do a compare?

Get-Command *compare*

shows we have compare-object.

If you haven’t seen this before – read the help.

In fact as a general rule read the help for all cmdlets before using in the games just to make sure you understand them and you’ve picked up on all the extras that might help you.

PS> get-help Compare-Object

SYNOPSIS
    Compares two sets of objects.

SYNTAX
    Compare-Object [-ReferenceObject] <PSObject[]> [-DifferenceObject] <PSObject[]>
   [-CaseSensitive] [-Culture <string>  ] [-ExcludeDifferent] [-IncludeEqual] [-PassThru]
   [-Property <Object[]>] [-SyncWindow <int>] [<CommonParameters>]

DESCRIPTION
    The Compare-Object cmdlet compares two sets of objects. One set of objects is the Reference set,
    and the other set is the Difference set.

    The result of the comparison indicates whether a property value appeared only in the object from
    the Reference set (indicated by the <= symbol), only in the object from the Difference set
   (indicated by the => symbol) or, if the IncludeEqual parameter is specified, in both objects
   (indicated by the == symbol).

 

This means we need to treat the folder contents as objects. PowerShell has a very clever trick we can use here. We put the

Get-ChildItem -Path c:\1
Get-ChildItem -Path c:\2

in parentheses which tells PowerShell to treat them as objects and we can use these directly as input to compare-object.

PS> Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)

InputObject                                                 SideIndicator
———–                                                 ————-
13.txt                                                      =>
10.txt                                                      <=

That should make sense now you can see what I mean. The alternative would be to do this

PS> $list1 = Get-ChildItem -Path c:\1
PS> $list2 = Get-ChildItem -Path c:\2
PS> Compare-Object -ReferenceObject $list1 -DifferenceObject $list2

InputObject                                                 SideIndicator
———–                                                 ————-
13.txt                                                      =>
10.txt                                                      <=

which involves more typing and depending on how you define complexity is more complex.

We can do the compare but we need to restrict the comparison to file names – so we need the –Property parameter.  This is why you need to read the help file.

Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)  -Property Name

This still produces the output above and we were told to output file objects. Once more into the help file and we find –Passthru which “Passes the objects that differed to the pipeline. By default, this cmdlet does not generate any output.”

PS> Compare-Object -ReferenceObject (Get-ChildItem -Path c:\1) -DifferenceObject (Get-ChildItem -Path c:\2)  -Property Name -PassThru

    Directory: C:\2

Mode                LastWriteTime     Length Name
—-                ————-     —— —-
-a—        14/04/2012     15:54          0 13.txt

    Directory: C:\1

Mode                LastWriteTime     Length Name
—-                ————-     —— —-
-a—        14/04/2012     15:54          0 10.txt

That solves the problem and is as simple as it gets. Oh you want aliases as well.

compare -R (ls c:\1) -Di (ls c:\2)  -Pr Name –Pa

And that is why I don’t like aliases because it is no where near as obvious what I am doing.


April 13, 2012  3:38 PM

Scripting Games 2012 comments: #7 update on beginners event 3

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

It was bugging me that I couldn’t get try-catch work on the folder where I didn’t have permissions. Finally thought of the reason – set the ErrorAction to Ignore. Heres the revised script

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   try {            
     $rt = New-Item -Path $path -ItemType Directory -ErrorAction Ignore            
   }            
   catch {            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)            
            
<# 
.SYNOPSIS
Creates a folder to hold a file containing process data

.DESCRIPTION
A folder - by default "C:\2012sg\event3" - is created and a file called
"Process3.txt" by default is created holding process data. The process 
name and id is used to fill the file. 

Any existing file is over written

If the folder can't be created in the root of C: it is created in
$env:HOMEDRIVE\$env:HOMEPATH i.e. the users home drive

.PARAMETER  path

Path of the folder we want to create
Default is "C:\2012sg\event3"

.PARAMETER  file

File name of file to hold process data
Default is "Process3.txt"

.EXAMPLE
.\beg3.ps1

Runs script with default values

.EXAMPLE
.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Runs script with values for path and file supplied

.EXAMPLE
.\beg3.ps1 -Verbose

Runs script with verbose output

#>


April 13, 2012  2:52 PM

Scripting games 2012 comments: #6 Beginners event 3

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

Onto event 3 http://blogs.technet.com/b/heyscriptingguy/archive/2012/04/04/2012-scripting-games-beginner-event-3-create-a-file-in-a-folder.aspx where we have to create a file in a folder. Simple – well yes but not with the twists the Scripting Guy throws at you.

The requirements:

  • save file in c:\2012sg\event3
  • file contains process name and id running on local machine
  • file must be called Process3.txt
  • if folder doesn’t exist must create it
  • if don’t have permissions to root of c: create it somewhere where you do have permissions
  • script should not generate errors that are ignored
  • script doesn’t need command line parameters but get extra points for including them
  • if include command line parameters should include comment based help and sample of using script

Lets start with the file contents. We can find the process information like this

Get-Process

Use a select to restrict the data to the two properties we want
Get-Process | select Name, Id

That looks ugly so lets bring the two columns together so that it’s easier on the eye.
Get-Process | Format-Table Name, Id  -a

The PowerShell object police will be screaming at this point because I’m not outputting objects BUT the data is going to end up in a file so it doesn’t matter.

Now we need to get the data into the file

PS> Get-Process | Format-Table Name, Id  -a | Out-File -FilePath Process3.txt
PS> Get-Content Process3.txt

Name                      Id
—-                      —
armsvc                  1764
BingApp                 3216
BingBar                 3764
bingsurrogate           2692

etc

Even kept the case on the file name!

So we can create a file with the data we need. What about the folder?

It has to be a specific path and if the path doesn’t exist we have to create it. Need to test for existence and create folder if it doesn’t exist.

The trick here is to test for the whole folder path. Ignore the file we are going to create that anywhere. Don’t test for each element in the path – if  c:\2012sg\event3 exists then c:\2012 MUST exist. We don’t care about the file because it will be over written if it exists.

There are a number of cmdlets in PowerShell for working with paths

PS> Get-Command *path

CommandType     Name
———–     —-
Cmdlet          Convert-Path
Cmdlet          Join-Path
Cmdlet          Resolve-Path
Cmdlet          Split-Path
Cmdlet          Test-Path

We want test-path. If the path DOESN’T exist we need to create it. To get the neatest code we test on the negative of its existence and create the folder

$path = "C:\2012sg\event3"

if (-not (Test-Path -Path $path)){
  New-Item -Path "C:\2012sg\event3" -ItemType Directory
}

Test-Path -Path $path

This will return True when we test the second time. The folder information is written to screen

    Directory: C:\2012sg

Mode                LastWriteTime     Length  Name                              
—-                ————-     ——  —-

d—-         13/04/2012     19:47            event3                      

We can suppress this by adding Out-Null onto the New-Item line

New-Item -Path "C:\2012sg\event3" -ItemType Directory | Out-Null

At this time we have the basis of our entry

if (Test-Path -Path C:\2012sg){            
  Remove-Item -Path "C:\2012sg" -Recurse -Force             
}            
            
$path = "C:\2012sg\event3"            
$file = "Process3.txt"            
            
if (-not (Test-Path -Path $path)){            
  New-Item -Path "C:\2012sg\event3" -ItemType Directory |             
  Out-Null            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

The first test-path deletes the folder AND contents if it exists. This is useful when testing to ensure you always end up testing the script properly but don’t forget to remove it at some stage so you can test that the script works if the folder exists!

I used Join-Path to create the full path to the file – could have done “$path\$file” or even worse $path + “\” + $file

That has dealt with  the first four objectives. Now what if we don’t have permissions to the root of C:

I’m not messing with the permissions on my C: drive but I’ve created a test folder and denied the permissions to create folders and files – typical permissions

This is what happens

PS> New-Item -Path c:\testperm -Name Myfolder -ItemType Directory

New-Item : Access to the path ‘Myfolder’ is denied.

At line:1 char:9

+ New-Item <<<<  -Path c:\testperm -Name Myfolder -ItemType Directory

    + CategoryInfo          : PermissionDenied: (C:\testperm\Myfolder:String) [New-Item], UnauthorizedAccessException

    + FullyQualifiedErrorId : CreateDirectoryUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand

Now we could use Get-Acl to test permissions

PS> Get-Acl -Path c:\testperm | select -ExpandProperty Access |

Format-Table IdentityReference, FileSystemRights, AccessControlType -AutoSize

IdentityReference                           FileSystemRights AccessControlType

—————–                           —————- —————–

BUILTIN\Administrators               CreateFiles, AppendData              Deny

BUILTIN\Administrators                           FullControl             Allow

BUILTIN\Administrators                             268435456             Allow

NT AUTHORITY\SYSTEM                              FullControl             Allow

NT AUTHORITY\SYSTEM                                268435456             Allow

BUILTIN\Users                    ReadAndExecute, Synchronize             Allow

NT AUTHORITY\Authenticated Users         Modify, Synchronize             Allow

NT AUTHORITY\Authenticated Users                  -536805376             Allow

But in reality we don’t know what permissions have been set so potentially we would have to test a lot of permissions. Not easy to get right and we don’t want to generate errors that are ignored.

We also need somewhere to put the file if we can’t get to C:\

You might think that this would work

path = "C:\Testperm\2012sg\event3"

$file = "Process3.txt"

if (-not (Test-Path -Path $path)){

  try {

    New-Item -Path $path -ItemType Directory |

    Out-Null

  }

  catch {

    write-host "oops"

  } 
}

Get-Process |

Format-Table Name, Id  -a |

Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

But it doesn’t because the error message thrown by New-Item isn’t a .NET exception. Its a message to say that the task hasn’t completed

Lets try this

$path = "C:\Testperm\2012sg\event3"

if (-not (Test-Path -Path $path)){

   $rt = New-Item -Path $path -ItemType Directory

}

$rt

$path = "C:\2012sg\event3"

if (-not (Test-Path -Path $path)){

   $rt = New-Item -Path $path -ItemType Directory

}

$rt

Which captures the two return messages

New-Item : Access to the path ‘event3′ is denied.

At line:4 char:18

+    $rt = New-Item <<<<  -Path $path -ItemType Directory

    + CategoryInfo          : PermissionDenied: (C:\Testperm\2012sg\event3:String) [New-Item], UnauthorizedAccessException

    + FullyQualifiedErrorId : CreateDirectoryUnauthorizedAccessError,Microsoft.PowerShell.Commands.NewItemCommand

 


    Directory: C:\2012sg


Mode                LastWriteTime     Length Name   
—-                ————-     —— —-  
d—-        13/04/2012     20:29            event3   

Working with either of these isn’t going to be easy BUT if the directory doesn’t create we get a error which we can access through the $error automatic variable

$path = "C:\2012sg\event3"            
$file = "Process3.txt"            
            
if (-not (Test-Path -Path $path)){            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

Set the folder creation to silently continue and test the last error message for part of the folder path – if its there something has gone wrong so we swing to creating the folder where we will have permissions – our home drive. A bit of manipulation with the path cmdlets and we have a new path where we can create the folder

get-process can then be used as before

That takes care of the folder creation if no permissions and the error messages being unhandled

At the moment the paths are hard coded but if we want to change then using  command line parameters is the best bet. Keep the current as defaults.

 

param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

We can use it like this to use the defaults

.\beg3.ps1

or like this to put the file somewhere else

.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Lets add some comments  – don’t use # – use write-debug and write-verbose

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)

Which means we can do this – all for adding [CmdletBinding()] to the top of the script

PS> .\beg3.ps1 -Verbose

VERBOSE: Test path is valid

VERBOSE: Write Process data

last job is add the help

[CmdletBinding()]            
param (            
 [string]$path = "C:\2012sg\event3",            
 [string]$file = "Process3.txt"            
)            
            
Write-Verbose "Test path is valid"            
if (-not (Test-Path -Path $path -IsValid)){            
  Throw "Invalid path $path"            
}            
            
if (-not (Test-Path -Path $path)){            
   Write-Verbose "Create Folder"            
   $rt = New-Item -Path $path -ItemType Directory -ErrorAction SilentlyContinue            
   if (Select-String -InputObject $error[0] -Pattern "event3" -SimpleMatch){            
     $path = (Join-Path -Path $env:HOMEDRIVE -ChildPath $env:HOMEPATH) + (Split-Path -Path $path -NoQualifier)            
                 
     Write-Debug $path            
     New-Item -Path $path -ItemType Directory | Out-Null            
   }            
}            
            
Write-Verbose "Write Process data"            
Get-Process |             
Format-Table Name, Id  -a |             
Out-File -FilePath (Join-Path -Path $path -ChildPath $file)            
            
<# 
.SYNOPSIS
Creates a folder to hold a file containing process data

.DESCRIPTION
A folder - by default "C:\2012sg\event3" - is created and a file called
"Process3.txt" by default is created holding process data. The process 
name and id is used to fill the file. 

Any existing file is over written

If the folder can't be created in the root of C: it is created in
$env:HOMEDRIVE\$env:HOMEPATH i.e. the users home drive

.PARAMETER  path

Path of the folder we want to create
Default is "C:\2012sg\event3"

.PARAMETER  file

File name of file to hold process data
Default is "Process3.txt"

.EXAMPLE
.\beg3.ps1

Runs script with default values

.EXAMPLE
.\beg3.ps1 -path "c:\testbeg3\event3" -file "testP3.txt"

Runs script with values for path and file supplied

.EXAMPLE
.\beg3.ps1 -Verbose

Runs script with verbose output

#>

I always add help at the end. check about_comment_based help for further details


April 13, 2012  7:47 AM

UK PowerShell Group–24 April 2012

Richard Siddaway Richard Siddaway Profile: Richard Siddaway

This months meeting is the first of two looking at the new PowerShell functionality in Windows Server 8. Yes, there is so much it will take two sessions!


When: Tuesday, Apr 24, 2012 7:30 PM (BST)


Where: Virtual

*~*~*~*~*~*~*~*~*~*

This is the first of two meetings looking at the new PowerShell functionality available in Windows Server 8

Notes


Richard Siddaway has invited you to attend an online meeting using Live Meeting.
Join the meeting.
Audio Information
Computer Audio
To use computer audio, you need speakers and microphone, or a headset.
First Time Users:
To save time before the meeting, check your system to make sure it is ready to use Microsoft Office Live Meeting.
Troubleshooting
Unable to join the meeting? Follow these steps:

  1. Copy this address and paste it into your web browser:
    https://www.livemeeting.com/cc/usergroups/join
  2. Copy and paste the required information:
    Meeting ID: S5NC59
    Entry Code: c^]”2]44W
    Location: https://www.livemeeting.com/cc/usergroups

If you still cannot enter the meeting, contact support

Notice
Microsoft Office Live Meeting can be used to record meetings. By participating in this meeting, you agree that your communications may be monitored or recorded at any time during the meeting.


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: