I've been looking at the File Server Resource Manager (FSRM) feature in Windows Server 2008 R2 a lot lately. One very nice part of FSRM is the ability to schedule typical file management tasks. One of the examples from the Microsoft storage team is to create a custom task to move old files to another server and leave a shortcut in place. The files are moved to a more appropriate server and the users can still find their files with practically no fuss. However, this requires Windows Server 2008 R2. If you aren't there yet, you can use my PowerShell function, New-FileShortCut in your archiving process.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
This will require PowerShell 2.0. But you should be able to run it from your Windows 7 desktop and manage a Windows Server 2003 file server.
1: Function New-FileShortcut {
2: #requires -version 2.0
3:
4: <#
5: .Synopsis
6: Create a file link shortcut.
7: .Description
8: This function will create a file shortcut link, a .lnk file. This can be useful when
9: moving files to another location and you want to leave a shortcut behind in place
10: of the orginal file. You must specify a shortcut name ending in .lnk and the target
11: file must already be in place.
12:
13: This function fully supports -verbose, -confirm and -whatif.
14:
15: .Parameter Name
16: The full filename and path of the shortcut file. It must end in .lnk. You
17: should specify the complete path.
18: .Parameter Target
19: The full filename and path of the target file. It must exist as it will be
20: verified during the validation process.
21: .Parameter Passthru
22: By default nothing is written to the pipeline. Use -Passthru to write the shortcut
23: file object to the pipeline.
24: .Example
25: PS C:\> New-FileShortcut \\file01\files\MyData.lnk \\file02\Archive\MyData.doc
26:
27: This will create a shortcut file for MyData on FILE01 that points to the actual
28: file on FILE02.
29: .Example
30: PS C:\> dir H:\Files\*.csv | foreach {
31: $dest="\\File02\Archive\files"
32: Move-Item $_ -destination $dest
33: New-FileShortcut -name (Join-Path $_.Directory "$($_.basename).lnk" -target (Join-path $dest $_.name) -Passthru
34: }
35:
36: This example will move all CSV files from H:\Files to the Archive folder on FILE02. After each file is moved
37: a new shortcut link is left in its place. Join-Path is used to construct valid file paths and names.
38:
39: .Inputs
40: None
41: .Outputs
42: File object if you use -Passthru
43:
44: .Link
45: Get-Item
46:
47: .Notes
48: NAME: New-FileShortcut
49: VERSION: 1.1
50: AUTHOR: Jeffery Hicks
51: LASTEDIT: 3/17/2010
52:
53: #>
54:
55: [cmdletBinding(SupportsShouldProcess=$True,ConfirmImpact="Low")]
56:
57: Param (
58: [Parameter(Mandatory=$True, ValueFromPipeline=$False,Position=0,
59: HelpMessage="Enter the name of the file shortcut. It must end in .lnk")]
60: [ValidateScript({$_.EndsWith(".lnk")})]
61: [string]$Name,
62:
63: [Parameter(Mandatory=$True, ValueFromPipeline=$False,Position=1,
64: HelpMessage="Enter the full path to the target file shortcut. The file must already exist.")]
65: [ValidateScript({Test-Path $_})]
66: [string]$Target,
67:
68: [Switch]$PassThru
69:
70: ) #end Parameter definition
71:
72: Begin {
73: write-verbose "Starting function"
74: write-verbose "Creating the shell COM Object"
75: $wshell=New-Object -ComObject "Wscript.Shell"
76: } #end Begin scriptblock
77:
78: Process {
79: write-verbose "Creating a shortcut for $target as $name"
80: #shortcut will be saved in System32 folder unless a full path
81: #is specified. If the user just puts in a filename, assume
82: #they want the file in the current directory.
83:
84: if (split-path $name -Parent) {
85: $linkpath=$name
86: }
87: else {
88: $linkpath=Join-path (get-location) $name
89: write-verbose "Adjusting link file to $linkpath"
90: }
91:
92: $shortcut=$wshell.CreateShortcut($linkpath)
93: $shortcut.TargetPath=$target
94:
95: if ($pscmdlet.ShouldProcess($Target)) {
96: write-verbose "Saving shortcut"
97: $shortcut.Save()
98:
99: if ($passthru) {
100: write-verbose "Passing shortcut file object to the pipeline"
101: #write the new shortcut file object to the pipeline if -Passthru
102: Get-item $shortcut.fullname
103: }
104: }
105:
106: } #end Process scriptblock
107:
108: End {
109: write-verbose "Removing the shell object"
110: Remove-variable wshell -confirm:$false
111: write-verbose "Ending function"
112: } #end End scriptblock
113:
114: } #end Function
The function includes comment-based help to make it easier to understand and use.
I’ve also taken the liberty of including support for –Verbose, –WhatIf and –Confirm. Not that creating a shortcut is that serious but I thought it might be worthwhile as a demonstration. These are defined with the cmdletBinding statement.
[cmdletBinding(SupportsShouldProcess=$True,ConfirmImpact="Low")]
The Write-Verbose lines only execute if you use –Verbose. The SupportsShouldProcess setting is used when –WhatIf is specified. This block of code is configured to handle –WhatIf.
1: if ($pscmdlet.ShouldProcess($Target)) {
2: write-verbose "Saving shortcut"
3: $shortcut.Save()
Using –WhatIf turns line 1 into a False statement so the shortcut is never saved. The method value I’m using, $Target, is what PowerShell displays in the WhatIf statement. I could have used any variable. In my case it made more sense to show the target file. If –Whatif isn’t specified then this line is True and the file is saved.
The ConfirmImpact setting tells PowerShell to prompt me if –Confirm is passed. If you always want to be confirmed, you can set this value to “High”.
The function itself is a wrapper for the Wscript.Shell COM object that you use in VBScript. This object has a CreateShortcut() method. All my function is doing is creating a simple shortcut. By default the function doesn’t write anything to the pipeline, but you can use –Passthru to see the actual file. I couldn’t think of a good situation where you might want to use this in a pipelined expression so I kept it simple. Feel free to edit the function if you want to reverse this behavior.
Alright. Now that we have this function how can we use it in our file management task? Conceptually you would use Get-Childitem to retrieve what ever set of files you want to manage. Move them to the new location then create a shortcut in the original directory that points to the moved file.
1: dir \\File01\Files\*.csv | foreach {
2: $dest="\\File02\Archive\files"
3: Move-Item $_ -destination $dest
4: $link=(Join-Path $_.Directory "$($_.basename).lnk"
5: $target=(Join-path $dest $_.name)
6: New-FileShortcut -name $link -target $target -Passthru
7: }
8:
In this example I’m getting all CSV files from \\File01\Files. Each file is moved to the new destination and to keep it easy to follow I' define values for the link file (did I mention it must end in .lnk?) and the target file. I want to make sure the link file keeps the same basename and is in the same directory. But just like that all the CSV files are moved, leaving shortcut links in their place. When the user goes to the share and double clicks the CSV icon, they’ll actually open the file from the other server.
You can download the script file here.
Oops. Wrong file attached.
Now I have the right file in the right formatting.