Avatar of sylar
sylar
Flag for United States of America asked on

Need help with powershell script to batch update specific shortcut targets and their actual filenames (ie: .LNK shortcut files)

Please help! - I need your assistance with coding a powershell script to batch update specific .LNK shortcut targets as well as their .LNK filenames.  I would also like for the powershell script to request input from the admin such as filename / filetype, Folder Target, OldserverName and NewServerName to pass to application via dialogue input boxes and use this input to take action otherwise it should allow to pass options to script via cmdline as well as provide the option of hardcoding into script to run automated.

For example; I have a TEST parent folder which has many .LNK shortcut files scattered in "D:\TEST" parent folder along with sub-folders which also contain misc. LNK files along with many other file types.

I would like for the powershell script to search specifically for the filename in question in our case "*.LNK" in "D:\TEST" and be recursive and identify any .LNK that contain the OldServerName in this example "\\oldserver" and *Replace* it with NewServerName such as "\\newserver".  Currently the below script will accomplish this without any issues.

However; If one of the files for example was called "Accounting on OldServer.lnk" and had a target of "\\oldserver\accounting" - the above script would currently update the file to point to "\\newserver\accounting" however - the name currently would remain as "Accounting on OldServer.lnk" instead of changing to "Accounting on NewServer.lnk"

I would also like for the script to audit every change it makes in an HTML LOG file.  So I would have an audit log of what changes it has completed.  It would be nice if it can create an HTML FILE that would have the below information:

Old Filename                                  New Filename                                  Old Target Path                                       New Target Path
Accounting on OldServer.lnk       Accounting on NewServer.lnk           \\oldserver\accounting                          \\newserver\accounting
HR on Oldserver.lnk                     HR on NewServer.lnk                       \\oldserver.domain.local\hr                \\newserver.domain.local\hr


It would be nice to have some type of error protection should below be example be encountered:

Old Filename:        IS on Oldserver.lnk
Old Target Path:   \\oldserver.domain.local\IS

Old Filename:        Sales on Oldserver.lnk
Old Target Path:    \\oldserver\Sales

And I type in for old server name "\\oldserver" and for new server name I type in "newserver.domain.local" - below would be the result which would cause a problem with "IS" shortcut.

New Filename:         IS on NewServer.lnk
New Target Path:    \\newserver.domain.local.domain.local\IS             --->  This would be invalid - due to no error protection.

New Filename:        Sales on NewServer.lnk
New Target Path:    \\newserver.domain.local\Sales


Below is script I currently have:
$fileName ="*.LNK" 
$folder = "D:\TEST" 
[string]$fromunc = "\\oldserver" 
[string]$tounc = "\\newserver" 
$list = Get-ChildItem -Path $folder -Filter $fileName -Recurse  | Where-Object { $_.Attributes -ne "Directory"} | select -ExpandProperty FullName 
$obj = New-Object -ComObject WScript.Shell 
 
ForEach($lnk in $list) 
      { 
      $obj = New-Object -ComObject WScript.Shell 
      $link = $obj.CreateShortcut($lnk) 
      [string]$path = $link.TargetPath  
      [string]$path = [string]$path.Replace($fromunc.tostring(),$tounc.ToString()) 
      $link.TargetPath = [string]$path 
      $link.Save()
  }

Open in new window

Powershell* CodingScripting Languages

Avatar of undefined
Last Comment
Qlemo

8/22/2022 - Mon
Shaun Vermaak

Do you have Active Directory?
sylar

ASKER
Shaun:   Thank you for comment.  Yes, I am in an AD environment.
Shaun Vermaak

You can manage these shortcut via AD GPO User/Computer shortcuts
All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
sylar

ASKER
Shaun:    Yes I am aware however these particular shortcuts which run in hundreds and are located everywhere I am not looking to manage via GPO and need a solution which works via a script that I can run once upon migrating to a new server.  I do not want to rely on AD GPO in my environment.
Shaun Vermaak

$fileName ="*.LNK" 
$folder = "D:\TEST" 
[string]$fromunc = "oldserver" 
[string]$tounc = "newserver" 
$list = Get-ChildItem -Path $folder -Filter $fileName -Recurse  | Where-Object { $_.Attributes -ne "Directory"} | select -ExpandProperty FullName 
$obj = New-Object -ComObject WScript.Shell 
 
ForEach($lnk in $list) 
      { 
      $obj = New-Object -ComObject WScript.Shell 
      $lnkName = [string]$lnk.Replace($fromunc.tostring(),$tounc.ToString()) 
      $link = $obj.CreateShortcut($lnkName) 
      [string]$path = $link.TargetPath  
      [string]$path = [string]$path.Replace("\\$($fromunc.tostring())","\\$($tounc.ToString())") 
      $link.TargetPath = [string]$path 
      $link.Save()
  }

Open in new window

sylar

ASKER
Shaun:    Thanks again for your help.   I have tried the above code - however; it does not seem to update the Target unc path at all - even though I can see the modified date of the shortcut changes to current date/time.  Also the filename of the actual shortcut is also not updated.  If I use my original code - I am able to update all the shortcuts with the updated Target unc path.
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
Qlemo

Honestly, looking whether to use the domain or not makes things more complex as it is worth it, as you are under full control of the replacement parts - just don't use the domain there.
I've removed a lot of unnecessary stuff.
$folder  = "D:\TEST" 
$fromSrv = "Oldserver" 
$toSrv   = "Newserver"

# static code
$wShell = New-Object -ComObject WScript.Shell 

Get-ChildItem -File -Path $folder -Filter "*.lnk" -Recurse |
% {
  $link = $wShell.CreateShortcut($_.FullName)
  $log = PsCustomObject @{'Location'        = $_.DirectoryName
                          'Old Filename'    = $_.Name
                          'New Filename'    = $null
                          'Old Target Path' = $link.Targetpath
                          'New Target Path' = $null}

  $link.TargetPath       = $link.TargetPath       -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.WorkingDirectory = $link.WorkingDirectory -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.IconLocation     = $link.IconLocation     -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.Save()
  $log.'New Target Path' = $link.TargetPath
  $link = $null

  $log.'New FileName' = $_ | Rename-Item -NewName ($_.Name -replace $fromSrv, $toSrv) -PassThru | Select -Expand Name

  $log
} | ConvertTo-HTML C:\Temp\Results.html

Open in new window

The HTML log should be in C:\Temp\Results.csv, with basic formatting only.
The script requires PowerShell 4, but we can change that if necessary.
sylar

ASKER
Qlemo:   I LOVE it.   Many thanks for your help.  The script originally errored out in two locations but I was able to identify the issues and fixed below:

Original code snippet:
$log = PsCustomObject @{'Location'        = $_.DirectoryName

changed to:
$log = [PsCustomObject] @{'Location'        = $_.DirectoryName

Original code snippet:
} | ConvertTo-HTML C:\Temp\Results.html

changed to:
} | ConvertTo-HTML | Out-File C:\Temp\Results.html

I do have two additional concerns:
1.   After testing the script on hundreds of .LNK files in the test folder / sub-folders, I noticed the script will update the modification date for .LNK that really do NOT match the old server criteria.  Based on the filter currently it looks like it will simply look for any LNK files - and NOT skip .LNK files that do not have the "oldserver" server name in target.  Is there a way to produce logic into the modified script that would account for that.  This way the script will skip over any .LNK files that DO NOT match the criteria.

2.    For the section below:
$folder  = "D:\TEST"
$fromSrv = "Oldserver"
$toSrv   = "Newserver"

Is there anyway we can make the script more interactive of which we can allow for it to accept parameters via cmdline and if none are passed to it, it will open a GUI dialogue box requesting for Folder Name, FromServer and ToServer reading in the defaults from the actually settings within the powershell script.  This way should I want to accept the defaults I can just hit enter all thru the dialogue boxes?

The current script now reads as below:
$folder  = "D:\TEST" 
$fromSrv = "Oldserver" 
$toSrv   = "Newserver"

# static code
$wShell = New-Object -ComObject WScript.Shell 

Get-ChildItem -File -Path $folder -Filter "*.lnk" -Recurse |
% {
  $link = $wShell.CreateShortcut($_.FullName)
  $log = [PsCustomObject] @{'Location'        = $_.DirectoryName
                          'Old Filename'    = $_.Name
                          'New Filename'    = $null
                          'Old Target Path' = $link.Targetpath
                          'New Target Path' = $null}

  $link.TargetPath       = $link.TargetPath       -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.WorkingDirectory = $link.WorkingDirectory -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.IconLocation     = $link.IconLocation     -replace ('\\'+$fromSrv), ('\\'+$toSrv)
  $link.Save()
  $log.'New Target Path' = $link.TargetPath
  $link = $null

  $log.'New FileName' = $_ | Rename-Item -NewName ($_.Name -replace $fromSrv, $toSrv) -PassThru | Select -Expand Name

  $log
} | ConvertTo-HTML | Out-File C:\Temp\Results.html

Open in new window

Qlemo

Stupid errors, those :<. Great you were able to correct that yourself. I appreciate talking to someone who understands the code :D.

Of course we can improve the script. That it touches all links did not get my attention because the original script did it the same way ;-). Assuming that we only change links where the target path needs adjustment, we can filter on a lot of ways. With the current code is easier to apply some checks inside the foreach-object loop than to choose alternatives. Luckily we have old and new targetpath at hand, and can compare those ...
So: first step is to replace lines 20-26 with:
  $log.'New Target Path' = $link.TargetPath
  if ($log.'Old Taget Path' -ne $log.'New Target Path')
  {
    $link.Save()
    $log.'New FileName' = $_ | Rename-Item -NewName ($_.Name -replace $fromSrv, $toSrv) -PassThru | Select -Expand Name
    $log
  }
  $link = $null

Open in new window

Now for your other improvement request. Yes, we can make the script use parameters, and prompt if any of them has not been provided. But I won't use a GUI for that - constructing a real input form isn't done easily (manually, without using a wizard). The most simple way is to use  a text prompt, the next to use default input prompt windows.
I would approach it like this - replace the first three lines with:
param([String] $folder, [String] $fromSrv, [String] $toSrv)

$defaults = @{folder  = 'D:\TEST'
              fromSrv = 'Oldserver'
              toSrv   = 'Newserver'}

if (!$folder ) { $folder  = Read-Host "Please provide a folder name to start the link search in [$($defaults['folder'])]" }
if (!$fromSrv) { $fromSrv = Read-Host "Please provide the old server name [$($defaults['fromSrv'])]" }
if (!$tosrv  ) { $toSrv   = Read-Host "Please provide the new server name [$($defaults['toSrv'  ])]" }

if (!$folder ) { $folder  = $defaults['folder' ] }
if (!$fromSrv) { $fromSrv = $defaults['fromSrv'] }
if (!$toSrv  ) { $toSrv   = $defaults['toSrv'  ] }

Open in new window

You can now call the file with or without parameters, named or not:
.\change-links.ps1  'D:\TEST' 'Oldserver' 'Newserver'                         # note: sequence of values important!
.\change-links.ps1  -folder 'D:\TEST' -fromSrv 'Oldserver' -toSrv 'Newserver' # note: sequence does not matter
.\change-links.ps1  'D:\TEST' -fromSrv 'Oldserver' -toSrv 'Newserver'         # and mixed

Open in new window

As noted, you can provide parameters with names in any sequence, but without you need to keep it. So you can write
.\change-links.ps1  -toSrv 'Newserver'  -fromSrv 'Oldserver' 'D:\TEST'
.\change-links.ps1  -fromSrv 'Oldserver'  'D:\TEST' -toSrv 'Newserver' 
.\change-links.ps1  'D:\TEST' 'Oldserver' -toSrv 'Newserver' 
# but not
.\change-links.ps1  'Oldserver'  'D:\TEST' -toSrv 'Newserver' 

Open in new window

Your help has saved me hundreds of hours of internet surfing.
fblack61
sylar

ASKER
Qlemo:   Thank you for the prompt response.  You are a genius and so helpful.   Everything is working great with the exception of I am still noticing any .LNK shortcut files that do not currently have a target of what I am filtering for would have their dates modified to recent date and time even though we have NOT updated the target URL...

For ex:   I have 5 .LNK files in a directory named D:\TEST

Sales on Oldserver-1.lnk    (Target:  \\Oldserver\sales)    :  Date:     3-18-2017 / Time:   5:00PM
Sales on Oldserver-2.lnk    (Target:  \\Oldserver\sales)    :  Date:     3-18-2017 / Time:   5:00PM
Sales on Oldserver-3.lnk    (Target:  \\Oldserver\sales)    :  Date:     3-18-2017 / Time:   5:00PM
Sales on Oldserver-4.lnk    (Target:  \\Oldserver\sales)    :  Date:     3-18-2017 / Time:   5:00PM
Test on LABserver-1.lnk    (Target:  \\LABserver\test)       :  Date:     3-18-2017 / Time:   5:00PM

When I run the script against these .LNK files - all 5 files are updated with below:

Sales on Newserver-1.lnk    (Target:  \\Newserver\sales)    :  Date:     3-18-2017 / Time:   9:00PM
Sales on Newserver-2.lnk    (Target:  \\Newserver\sales)    :  Date:     3-18-2017 / Time:   9:00PM
Sales on Newserver-3.lnk    (Target:  \\Newserver\sales)    :  Date:     3-18-2017 / Time:   9:00PM
Sales on Newserver-4.lnk    (Target:  \\Newserver\sales)    :  Date:     3-18-2017 / Time:   9:00PM
Test on LABserver-1.lnk       (Target:  \\LABserver\test)       :  Date:     3-18-2017 / Time:   9:00PM

Notice even though the script was smart enough to identify that 'Test on Labserver-1.lnk' target does not match what we are filtering for it somehow continues to either rewrite the original contents or touch the file... I thought it was a windows bug - so I attempted to manually try to right-click on a shortcut look at the target then cancel out of the properties to see if just accessing the shortcut would cause that.  The great news it did not.  I then right-clicked the file again but this time instead of clicking cancel - I simply clicked 'OK' just to see if that would somehow cause the date/time to change - and the great news it did NOT.

I believe there is something we are missing with the code in which it is re-writing the original contents back out when it doesn't match which causes the date/time to update.  Could it be from the below snippet of code:

Get-ChildItem -File -Path $folder -Filter "*.lnk" -Recurse |
sylar

ASKER
Qlmo:     Additionally with the same theory in mind - I noticed if I re-run the script again after it is successful on the four *.LNK files that it has successfully updated the targets for - it will process and re-touch the same four *.LNK again - even though they NOW no longer match the filter and the script will change the date/time of the four files again on the second run.
ASKER CERTIFIED SOLUTION
Qlemo

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
sylar

ASKER
Hi Qlemo:   You're amazing! - This worked like a charm and hit every bullet-point that I was looking for.  Thanks for the quick turn-around buddy.  I have learned a lot just from your examples.
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
sylar

ASKER
Hi Qlemo:   You're amazing! - This worked like a charm and hit every bullet-point that I was looking for.  Thanks for the quick turn-around buddy.  I have learned a lot just from your examples.
Qlemo

You have been a good apprentice :D.