Tag Archives: Tools

One-liner: duplicate a folder collection, without files

New fiscal year; new set of empty folders, but with the same structure and permissions as the previous year? Robocopy to the rescue:

robocopy "FY 2015" "FY 2016" /e /xf * /COPY:DATSO /log:c:\temp\new-year-folders.log /tee

/e = copy all subdirectories, even empty ones.
/xf * = Exclude filenames matching *, i.e., all of them
/COPY:DATSO = what to copy: Data, Attributes, Timestamp, Security, and Owner.

I like to log things, so I include that, too. If you’re really cautious, you could do a dry run with the /L switch, which makes robocopy just log what it would do, but not actually perform any actions. Kind of like the PowerShell -whatif switch.

Robocopy file classes

This information comes from the Robocopy.exe documentation PDF file for Windows XP version, but it’s the best description I’ve been able to find. From page 15 of that document:

Using Robocopy File Classes

For each directory processed, Robocopy constructs a list of files in both the source
and destination directories. This list matches the files specified on the command line
for copying.

Robocopy then cross-references the lists, determining where files exist and comparing
file times and sizes. The program places each selected file in one of the following
classes.

File Class In source In destination Source/Dest file times Source/dest file sizes Source/dest attributes
Lonely Yes No n/a n/a n/a
Tweaked Yes Yes Equal Equal Different
Same Yes Yes Equal Equal Equal
Changed Yes Yes Equal Different n/a
Newer Yes Yes Source > Destination n/a n/a
Older Yes Yes Source < Destination n/a n/a
Extra No Yes n/a n/a n/a
Mismatched Yes (file) Yes (directory) n/a n/a n/a

By default, Changed, Newer, and Older files are candidates for copying (subject to
further filtering, as described later). Same files are not copied. Extra and Mismatched
files and directories are only reported in the output log.

Normally, Tweaked files are neither identified nor copied – they are usually identified
as Same files by default. Only when /IT is used will the distinction between Same and
Tweaked files be made, and only then will Tweaked files be copied.

PowerShell Script: New-RandomString.ps1

I need to automate the setting of passwords on some Active Directory accounts. Since resetting passwords is also a task that I’m asked to perform with some routine, I decided to make a more generic tool script that could be used in a variety of tasks ( I listened to Don Jones‘ advice on building Tools and Controllers).

I also got a head start from Bill Stewart’s useful Windows IT Pro article Generating Random Passwords in PowerShell.  Among the changes I made are source character class handling, and a new SecureString output option. Please let me know if you find the script useful, or if you find any bugs.

<#
.SYNOPSIS
Generates one or more randomized strings containing specified
character classes.

.PARAMETER Length
The length of the string to be generated.

.PARAMETER CharacterClasses
An array of Character Classes from which to generate the string. The string
will contain at least one character from each specificied class. You may also use the alias 'Classes' for the parameter name

Valid Character classes are:

    Upper    - A..Z
    Lower    - a..z
    Digits   - 0..9
    AlphaNum - shorthand for Upper,Lower,Digits
    Symbols  - !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
    Safe     - #$%+-./:=\_~  (ODBC Safe, Shell Safe if quoted)

If no classes are specified, a string is generated with mixed-case letters,
digits, and symbol characters (i.e., ALL the classes).

.PARAMETER IncludeCharacters
A string of characters to include in the generated string:

.PARAMETER ExcludeCharacters
A string a characters to exclude in the generated string:

.PARAMETER Count
The number of strings to be generated.

.PARAMETER AsSecureString
Specifies that the new random string(s) will be returned as Secure String
objects, to make their use as passwords easier.

.EXAMPLE
> New-RandomString.ps1 -CharacterClasses Lower,Digits -Length 14 -Count 5
Generated five strings, each fourteen characters long, comprised of lowercase
letters and digits.

.EXAMPLE
> New-RandomString.ps1 -Classes AlphaNum,Symbols -length 21
> New-RandomString.ps1 -length 21

The previous two commands are equivalent, because the default character classes
used are upper and lowercase letters, digits, and symbol characters.

.EXAMPLE
> New-RandomString.ps1 -Class 'AlphaNum' -Include '#$%^'

The generated string will contain characters from the UpperCase, LowerCase
and Digits classes, as well as at least one character from among the four
specified.

.EXAMPLE
> New-RandomString.ps1 -Class 'AlphaNum' -Exclude 'O0l1'

The generated string will contain characters from the UpperCase, LowerCase
and Digits classes, but will not contain the "look-alike' characters.

.Notes
Author     : Geoff Duke <Geoffrey.Duke@uvm.edu>
Last Edit  : 2014-11-07 

Based on script "Get-RandomString.ps1" for Windows IT Pro:

http://windowsitpro.com/powershell/generating-random-passwords-powershell

#>

#Requires -version 3

[cmdletbinding()]
Param(
        [alias('Size')]
        [ValidateRange(7,256)]
        [int]
        $length = 21,

        [int]
        $count = 1,

        [Parameter()]
        [ValidateSet('Upper','Lower','Digits','AlphaNum','Symbols','Safe')]
        [alias('Classes')]
        [String[]]
        $CharacterClasses = @('Upper','Lower','Digits','Symbols'),

        [Parameter()]
        [string]
        $IncludeCharacters = '',

        [string]
        $ExcludeCharacters = '',

        [switch]
        $AsSecureString

     )

Set-StrictMode -version 'Latest'

# Additional parameter wrangling
# --------------------------------------------------------------------
[string[]] $Classes = $CharacterClasses.ToLower()

if ( $Classes.Contains('safe') -and $Classes.Contains('symbols') ) {
    write-warning 'You specified both "Symbols" and "Safe" character classes; this is the same as just specifying "Symbols".'
    $Classes = $Classes | where { $_ -ne 'safe' }
}

# Replace alphanum with the upper,lower, and digits classes
if ( $Classes.Contains('alphanum') ) {
    $Classes = $Classes | where { $_ -ne 'alphanum' }
    $Classes += 'upper','lower','digits'
}

# remove any duplicated classes
$Classes = $Classes | select -unique

# Setup source characters
# -------------------------------------------------------------------- 

# Character classes - functionally, a strongly-typed hash of string arrays
#       (addresses issue of singleton arrays turning into simple strings)
$chars = New-Object 'Collections.Generic.Dictionary[string,char[]]'

$chars['lower']    =  97..122 | foreach-object { [Char] $_ }
$chars['upper']    =  65..90  | foreach-object { [Char] $_ }
$chars['digits']   =  48..57  | foreach-object { [Char] $_ }
$chars['symbols']  = (33..47+58..64+91..96+123..126) | foreach-object { [Char] $_ }
$chars['safe']     = '#$%+-./:=\_~'.ToCharArray()

write-verbose $( 'String must include a character from each of ' +
              $( $Classes -join ',' ) +
              $( if ( $IncludeCharacters ) { " plus [$IncludeCharacters] " } ) +
              $( if ( $ExcludeCharacters ) {
                  "but must not include any of [$ExcludeCharacters]" } ) )

if ( $IncludeCharacters ) {
    $Classes += 'include'
    $chars['include'] = $IncludeCharacters.ToCharArray()
}

[char[]] $char_source  = $chars[ $Classes ] | % { $_ } | select -unique

if ( $ExcludeCharacters ) {
    $char_source = $char_source | Where { $_ -NotIn $ExcludeCharacters.ToCharArray() }
}

write-verbose "Source chars: $(-join $char_source)"

# Generating the random string(s)
# --------------------------------------------------------------------
$string_count = 0
:NewString while ( $string_count -lt $Count )  {

    $output = ''
    for ( $i=0; $i -lt $length; $i++) {
        $output += get-random @($char_source)
    }
    write-debug "NewString: generated string is -> $output"

    # Ensure that the requested character classes are present
    :CharClass foreach ($class in $Classes) {
        foreach ( $char in $output.ToCharArray() ) {
            if ( $chars[$class] -Ccontains $char ) {
                write-debug "CharClass: '$char' is in $class"
                continue CharClass # check the next character class
            }
        } # end foreach $char, didn't match the current character class
        write-debug "CharClass: No character from $class! Start again"
        continue NewString # Need to generate a new string
    } # end foreach #class

    # string matches required character classes"
    $string_count++

    if ( $AsSecureString ) {
        ConvertTo-SecureString $output -AsPlainText -Force
    }
    else {
        $output
    }
} # end while

It was while I was writing this script that I ran into the Loop Label documentation error. In PowerShell, as in Perl, Loop Labels do not include the colon when used with a break or continue statement.

Set default printer with PowerShell

Closely related to my previous post, this simple script uses a WScript.Network COM object to set the default printer. The comment block is longer than the script, but I think it’s a useful little tool.

<#
.SYNOPSIS
Sets a Network Printer connection as the default printer.

.DESCRIPTION
Uses a COM object to sets the specified, installed printer as the default. If
an error is encountered, e.g., the specified printer isn't installed, the
exception is written to a file called Set-DefaultPrinter.err in the current
$env:temp directory, and then the script terminates, throwing the exception.

Based on my colleague's VBScript solution:

http://blog.uvm.edu/jgm/2014/06/11/parting-scripts-add-a-new-network-printer-and-set-it-as-default/

.PARAMETER PrinterShare
The UNC path to the shared printer.
e.g. \\printers1.campus.ad.uvm.edu\ETS-SAA-SamsungML-3560

.EXAMPLE
Set-DefaultPrinter.ps1 -PrinterShare '\\printers1.campus.ad.uvm.edu\ETS-SAA-SamsungML-3560'

.NOTES
    Script Name: Set-DefaultPrinter.ps1
    Author     : Geoff Duke <Geoffrey.Duke@uvm.edu>
#>

[cmdletbinding()]

Param(
    [Parameter(Mandatory=$true,
        HelpMessage="Enter the UNC path to the network printer")]
    [ValidateNotNullOrEmpty()]
    [string] $PrinterShare
)

Set-PSDebug -Strict

$PSDefaultParameterValues = @{"out-file:Encoding"="ASCII"}

$ws_net = New-Object -COM WScript.Network

try {
    $ws_net.SetDefaultPrinter($PrinterShare)
}
catch {
    $error[0].exception | out-file (join-path $env:temp 'Set-DefaultPrinter.err') 
    throw $error[0]
}
write-verbose "Default printer now $PrinterShare"

Add network printer with PowerShell

This is my PowerShwell translation of my colleague’s VBScript solution for mapping network printers with a script.

<#
.SYNOPSIS
Add a Network Printer connection, optionally making it the default printer.

.DESCRIPTION
Uses a COM object to add a Network Printer, and optionally sets that printer
as the default. If an error is encountered, the exception is written to a
file called Add-NetworkPrinter.err in the current $env:temp directory, and then
the script terminates.

This is my PowerShell translation of my colleague's VBScript solution:

http://blog.uvm.edu/jgm/2014/06/11/parting-scripts-add-a-new-network-printer-and-set-it-as-default/

.PARAMETER PrinterShare
The UNC path to the shared printer.
e.g. \\printers1.campus.ad.uvm.edu\ETS-SAA-SamsungML-3560

.PARAMETER Default
Specifies that the printer will also be set as the default printer for the current user.

.EXAMPLE
Add-NetworkPrinter.ps1 -PrinterShare '\\printers1.campus.ad.uvm.edu\ETS-SAA-SamsungML-3560' -Default

.NOTES
    Script Name: Add-NetworkPrinter.ps1
    Author     : Geoff Duke <Geoffrey.Duke@uvm.edu>
#>

[cmdletbinding()]

Param(
    [Parameter(Mandatory=$true,
        HelpMessage="Enter the UNC path to the network printer")]
    [ValidateNotNullOrEmpty()]
    [string] $PrinterShare,

    [parameter(Mandatory=$false)]
    [switch] $Default
)

Set-PSDebug -Strict

$PSDefaultParameterValues = @{"out-file:Encoding"="ASCII"}

$ws_net = New-Object -COM WScript.Network

write-verbose "Adding connection to $PrinterShare"
try {
    $ws_net.AddWindowsPrinterConnection($PrinterShare)
}
catch {
    $error[0].exception | out-file (join-path $env:temp 'Add-NetworkPrinter.err')
    throw $error[0]
}

write-verbose "Setting the printer as the default"
if ( $Default ) {
    try {
        $ws_net.SetDefaultPrinter($PrinterShare)
    }
    catch {
        $error[0].exception | out-file (join-path $env:temp 'Add-NetworkPrinter.err')
        throw $error[0]
    }
}

# the end

For use with Group Policy, it will probably be helpful to create a simple Set-DefaultPrinter.ps1 script. But that’s just the second stanza from the script above.

Quick tip – Human readable folder sizes in Excel

From the school of There’s Probably a Better Way, But…

I was taking the output of SysInternals’ du.exe utility and wanted to email it to a colleague, but the folder sizes were in KB. Since most of them were many Gigabytes in size, I wanted a quick way to convert them to the appropriate size in GB, MB, or KB.

I copied the du.exe size column and pasted it into Excel. Then I created the following formula:

=IF(E6>(2^20),TEXT(E6/(2^20),"0.0") & " GB","smaller")

…where E6 is the cell reference. Also note that du.exe’s output was in KB, so my test is one order of magnitude smaller than it would need to be if I was formatting a size in bytes. (i.e., 2^20 is 1 MB).

I then nested another similar if() function with a final formatting option to get my final formula:

=IF(E6>(2^20),TEXT(E6/(2^20),"0.0") & " GB",IF(E6>(2^10),TEXT(E6/(2^10),"0.0") & " MB",TEXT(E6,"0.0") & " KB"))

Powershell, ACLs, and DFS-N

I’m working on some storage issues with our file services, and DFS Namespace services may the the best solution. But I will need to be able to keep the permissions on the DFS folders with targets in sync with the permissions on the target folders. I’m hoping that the new DFS-N PowerShell commands will facilitate this process. However, on my Server 2012 test system, I can’t get the help content to download for the DFSN-related cmdlets.

I did find this gem in the PowerShell Tips of the Week archive:

Windows PowerShell Tip: Working With Security Descriptors

Good stuff.

Custom event log queries

I really like the newer event log model on Windows 2008 family, and the flexibility of the XML events and the queries that makes possible.

Recently, I started noticing a quiet failure of a scheduled task. The Task Scheduler thinks that the task completed successfully, though the executable called by the task action returned an error code of 3:

Task Scheduler successfully completed task “\ShareVol_Sync” , instance “{92ac3257-f52d-47eb-9a3a-ce02c5196bbd}” , action “diskshadow.exe” with return code 3.

I wanted to see how long this have been going on, so I switched from the Task Scheduler console to Eventlog Viewer, and navigated to the Operational log under “Applications and Services Logs”- Microsoft – Windows – TaskScheduler.

I started by using the using the Filter Current log dialog to select events with Event ID 201, but this included all “Action completed” events for all tasks. So I looked at the XML view for one of the events for the task I was researching. The event includes a data value named “ActionName” with the value “diskshadow.exe” that should allow me to find all the relevant events.

eventvwr-evt-xml

Next, I needed to refine my filter to look for this value in the events. I opened the Filter Current log dialog again, and switched to the XML tab, then checked the Edit query manually option. You get a scary warning about not being able to use the GUI again, but that only applies to the current filter. Be bold: click OK.

Next, I edited the query, following examples from this excellent Ask the Directory Services Team blog post. The query is junk the between the select tags. Originally, the query was simply:

*[System[(EventID=201)]]

To that, I added the following:

and
*[EventData[Data[@Name=’ActionName’] and (Data=’diskshadow.exe’)]]

So that the whole query looks like this:

<QueryList>
  <Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
    <Select Path="Microsoft-Windows-TaskScheduler/Operational">
      *[System[(EventID=201)]]
       and
      *[EventData[Data[@Name='ActionName'] and (Data='diskshadow.exe')]]
    </Select>
  </Query>
</QueryList>

Now event viewer shows me only the “Action Completed” events for the diskshadow.exe command, and I can see exactly when the behavior changed.

Note that you can save use the query XML with PowerShell’s Get-WinEvent commandlet’s -filterXML parameter [See an example]. You can also use the Save Filter to Custom View option to make this view persistent.

I routinely review Windows’ Event logs during diagnostics and troubleshooting. I find the ability to query those logs for specific data is an indispensable technique. No more dumping to CSV and running findstr! I hope you find it helpful, too.

Is that program running as administrator?

Using Process Explorer to view process integrity levels

A friend asked me how to open a Control Panel applet As Administrator. In Windows Vista, when you see a little shield icon as part of a button or shortcut, that would indicate that you would get prompted by the User Account Control (UAC) facility to elevate the process Integrity Level, that is, to run it as an administrator with full rights to muck with the system.

In Windows 7, the frequency of UAC prompts has been reduced. You will still see the shield icon, but sometimes there’s no UAC prompt.

You can use Microsoft SysInternals Process Explorer tool to view the integrity levels of running processes. On campus, you can run the tool from \\files\software\utilities\sysinternals\procexp.exe. Once you’ve started Process Explorer, there are two things you’ll want to do:

  1. From the File menu, select the Show Details for All Processes option (you noted the shield icon, yes?).
  2. From the View menu, choose Select Columns… and check Integrity Level item (on the Process Image tab; see below)

procexp-show-integrity

  Continue reading