Renaming directories with invalid names

Somehow, a client managed to create several directories with names that ended with a period. However, File Explorer and other tools (i.e., backup) are unable to access the folder contents, getting an error that usually is interpreted as “The system cannot find the file specified.”

According to KB2829981, the Win32_API is supposed to remove trailing space and period characters. KB320081 has some helpful suggestions, and also indicates that some techniques allow programs to bypass the filename validation checks, and some POSIX tools are not subject to these checks.

I found that I was able to delete these problem folders by using rmdir /q /s “\\?\J:\path\to\bad\folder.” But I wanted to rename the folders in order to preserve any content. After flailing about for a while, including attempts to modify the folders using a MacOS Client and a third-party SSH service on the host, I was prodded by my colleague Greg to look at Robocopy.

In the end, my solution was this:

  1. I enabled 8dot3 file name creation on a separate recovery volume (I didn’t want to do so on the multi-terabyte source volume)
  2. Using robocopy, I duplicated the parent folder containing the invalid folder names to the recovery volume, resulting in the creation of 8dot3 names for all the folders
  3. I listed the 8dot3 names of the problem folders with dir /x
  4. The rename command with the short name as a source and a valid new name

This fixed the folders, and let me access their contents. I then deleted the invalid folders from the source and copied the renamed folders into place.

It seems like a simple process, but I managed to waste most of a morning figuring this out. Hopefully, this may save someone else some time.

Troubleshooting Offline Files

My previous post describes the normal operation of Offline Files. And most of the time, “it just works.” But there are times when it won’t, and getting it running again can be challenging.

Two Important concepts

First, it’s important to understand that the Offline Files facility is providing a virtual view of the network folder to which Documents has been redirected when Windows detects that the network folder is unavailable. This means that, when Offline Files is really borked, users can see different things in their Documents folder depending one whether their computers are online or offline.

Second, Windows treats different names for the same actual server as if they are different servers altogether. Specifically, Windows will only provide the Offline Files virtual view for the path to the target network folder. You can see the target folder path in the Properties of the Documents folder.

The Location tab shows the UNC path to the target network folder.
The Location tab shows the UNC path to the target network folder.

For example, these two UNC paths resolve to the same network folder:

\\files.uvm.edu\rallycat\MyDocs
\\winfiles1.campus.ad.uvm.edu\rallycat\MyDocs

If the second path is the one that is shown in the Location tab in the properties of the Documents folder, then you will be able to access that path while offline, but not the first path.

Show me the logs

There are event logs that can be examined. I’ll mention them, but I’ve rarely found them helpful in solving a persistent problem. If you want to get the client up and running again ASAP, skip ahead to the Fix it section.

There are some logging options available that can help in diagnosing problems with offline files. There are two logs that are normally visible in the Windows Event Viewer, under the Applications and Services logs heading:

  • Microsoft-Windows-Folder Redirection/Operational
  • Microsoft-Windows-OfflineFiles/Operational

Continue reading →

Folder Redirection and Offline Files

The following information is not new. We are in the process of making changes to our Folder Redirection policy, though, and I thought it might be helpful to have this baseline information in a place that is handy for referral.

Background

Offline Files is a feature of Windows that was introduced in parallel with Folder Redirection in Windows 2000. Folder Redirection allows an administrator to relocate some of the user profile data folders to a network folder, which has the advantage of protecting that data from loss due to workstation issues like drive failure, malware infection, or theft. It also means you can access your data from multiple workstations.

The Offline Files facility provides a local cache of the redirected folder(s) so that mobile users can continue to work with the data in those folders when disconnected from the organization’s network. When the computer is connected to the network again, any changes to either the network folder or the local Offline Files cache are synchronized. Users are prompted to resolve any conflicting changes, e.g., the same file was modified in both places, or was deleted from one and modified in the other.

At UVM, we use Folder Redirection on the Documents folder (formerly My Documents in XP), as well as the Pictures, Video, and Music folders. Most of the time, the Offline Files facility works without issue. However, as with all technology, Offline Files can fail. There are circumstances that can result in the corruption of the database that Offline Files uses to track the sync status of files. Doing major reorganizing and renaming of files and folders, for example, seems to be a culprit. Another one is filling your quota; you can continue to save files to your local cache, but the files won’t get synced to the server because you’re out of space.

How to sync your offline files

To manually synchronize your Offline Files with the target network folder, open the Sync Center by:

  1. Going to the Start Screen (or menu) and typing sync center
  2. Clicking the Sync Center item in the search results

Windows 8.1 Start search for "sync center"

Windows 8.1 Start search for “sync center”

Windows 7 Start search for "sync center"

Windows 7 Start search for “sync center”

or

  1. Find the Sync Center tray icon and double-click it, or
  2. Right-click and select the Open Sync Center menu item

Menu for the Sync Center icon in the Windows system tray.

Menu for the Sync Center icon in the Windows system tray.

The Sync Center Window should appear.

Offline Files status in Sync Center

Offline Files status in Sync Center

Note that the Offline Files item shows the time of the most recent sync operation. If you want to initiate a sync operation, click Offline Files and then click Sync.

A sync operation has completed.

A sync operation has completed.

If there are errors or conflicts that require intervention to resolve, those will be show in the result. A conflict result is shown below.

Sync operation with a conflict.

Sync operation with a conflict.

Click the N Conflicts link or View sync conflicts on the left to see details about the files in conflict.

Right-click or select and click 'Resolve'.

Right-click or select and click “Resolve”.

Select each file conflict you want to resolve, and click Resolve or right-click the file and select View options to resolve.

Windows provides information about the files in conflict and provides several appropriate options.

Windows provides information about the files in conflict and provides several appropriate options.

In this scenario, a file has been deleted in one location, and modified while offline in the other. Since only the one file exists, there are only two options: delete the file, or copy it to both locations.

Another scenario involves a file have been modified both offline and online, probably while using multiple computers. In that case, the resolution Window offers three choices: pick the offline file (on this computer), pick the online version (on the network folder), or keep both by renaming one of them.

Sync Errors are handled differently, and may require the help of your IT support staff or the UVM Tech Team.

A sync operation with error.

A sync operation with error.

To review the errors or conflicts, you can view the Sync Results.

Sync result, with detail for an error.

Sync result, with detail for an error.

You can view details about an individual error by hovering over it with the mouse cursor. In the example above, my folder “2. Archive” is throwing an “Access is denied” error. To resolve an error like this, it may be necessary to contact the Tech Team. In some cases, it’s necessary to reset the Offline Files tracking database and essentially start over. This procedure is documented in a separate post, Troubleshooting Offline Files.

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.

PowerShell documentation error – loop labels

I’ve been banging my head on a problem with a script I’m writing. I want to stop executing an inner loop and resume with the next iteration of an outer loop. In Perl, I’d use a next statement with a loop label. In PowerShell, the analogous statement is continue, and loop labels are supported, as described in the about_Break help document.

I finally wrote simplified test code, following the documentation carefully. However, the documentation is wrong. It indicates that the break or continue statement should include the colon in the loop label. This doesn’t throw an error, but it executes as though the label isn’t present at all. The code below includes the colon.

$VerbosePreference = 'Continue'

write-warning 'There should be no output; the outer loop should be exited during first iteration'
:outer foreach ($a in ('red','green') ) {
    write-verbose "Outer loop"

    :inner foreach ($b in ('red','blue','green') ) {
        write-verbose "Inner loop"
 
        write-verbose "`$a is $a ; `$b is $b"
        if ( $a -eq $b ) {
            break :outer
        }
        "$a $b"
    }
}

Then cracked my copy of PowerShell in Action and saw that the loop label does not include the colon, just like Perl. Remove the colon and everything is good. Wish it hadn’t taken me hours to work it out.

 

Get-PrintJobs.ps1 PowerShell script

After a recent upgrade of our print servers, I discovered that the Print Spooler service event logging had been enhanced, and changed enough that some PowerShell reporting scripts that worked just fine on Windows Server 2008 (32-bit) no longer worked on Server 2012 R2.

To get the reports working again, I had to enable the Microsoft-Windows-PrintService/Operational log. I also had to increase the log size from the default in order to retain more than one day’s events. The trickiest part was figuring out the XPath query syntax for retrieving events from a particular printer. The newer syntax makes more sense to me, but it took me a long time to arrive at it.

Following Don Jones‘ entreaty to build tools and controllers, I offer this tool script, which retrieves (simplified) print job events, and cares not a whit about formatting or saving.


<#
.SYNOPSIS
Gets successful print job events and returns simplified objects with relevant
details.

.DESCRIPTION
Collects the successful print jobs from the PrintService Operational log, with
optional query parameters including Printer name and start and end times.

.PARAMETER PrinterName
The share name of the printer for which events will be retrieved.

.PARAMETER StartTime
The beginning of the interval during which events will be retrieved.

.PARAMETER EndTime
The end of the interval during which events will be retrieved.

.EXAMPLE
C:\> Get-PrintJobs.ps1
Returns objects representing all the successful print jobs (events with id 307).

.EXAMPLE
C:\> Get-PrintJobs.ps1 -PrinterName 'Accounting HP LaserJet'
Returns objects for all the jobs on the Accounting printer.

.EXAMPLE
C:\> Get-PrintJobs.ps1 -PrinterName 'Accounting HP LaserJet' -StartTime (Get-Date).AddHours(-12)
Returns objects for all the jobs on the Accounting printer generated in the last twelve hours.
.NOTES
Script Name: Get-PrintJobs.ps1
Author : Geoff Duke <Geoffrey.Duke@uvm.edu>

Edit 2014-10-08: Generalizing from dept printer report script, fixing XPath
query syntax.

Edit 2012-11-29: Job is run as SYSTEM, and computer object has been granted
Modify rights to the destination directory.
#>

Param(
[string] $PrinterName,

[datetime] $StartTime,

[datetime] $EndTime
)
Set-StrictMode -version latest
# Building XPath query to select the right events
$filter_start = @'
<QueryList>
  <Query Id="0" Path="Microsoft-Windows-PrintService/Operational">
    <Select Path="Microsoft-Windows-PrintService/Operational">
'@

$filter_end = @'
    </Select>
  </Query>
</QueryList>
'@

$filter_match = '*[System[(EventID=307)' #need to add ']]' to close

if ( $StartTime -or $EndTime) {
    $filter_match += ' and TimeCreated[' #need to add ']' to close
    $time_conds = @()

    if ( $StartTime ) {
        $time_conds += ( '@SystemTime&gt;=' +
            "'{0:yyyy-MM-ddTHH:mm:ss.000Z}'" -f $StartTime.ToUniversalTime()
        )
    }
    if ( $EndTime ) {
        $time_conds += ( '@SystemTime&lt;=' +
            "'{0:yyyy-MM-ddTHH:mm:ss.000Z}'" -f $EndTime.ToUniversalTime()
        )
    }

    $filter_match += ( $time_conds -join ' and ' ) + ' ]' # Closing TimeCreated[
}

$filter_match += "]]`n" # Closing [System[

if ( $PrinterName ) {
    $filter_match += @"
  and
*[UserData[DocumentPrinted[(Param5='$PrinterName')]]]
"@
}

write-debug "Using Filter:`n $filter_match"

# The $filter variable below is cast as XML, that's getting munged
# by WordPress or the SyntaxHighlighter as '1'
[xml] $filter = ($filter_start + $filter_match + $filter_end)

get-winevent -filterXML $filter | foreach {
    $Properties = @{
        'Time' = $_.TimeCreated;
        'Printer' = $_.Properties[4].value;
        'ClientIP' = $_.properties[3].value.SubString(2);
        'User' = $_.properties[2].value;
        'Pages' = [int] $_.properties[7].value;
        'Size' = [int] $_.properties[6].value
    }

    New-Object PsObject -Property $Properties
}

If you find this script useful, please let me know. If you find any bugs, definitely let me know!

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"