Reading Directory + Creating Shortcuts with PowerShell - powershell

everyone. I'm trying to figure out a way to read the contents of a directory in Windows and find files of a specific file extension ('.hod' in this case), then create shortcuts to each of those files into 'C:\Users\Public\Desktop.'
Below is an example I've been testing with so far in PowerShell (I know it looks utterly terrible). I'd appreciate any input. Thanks.
$shortcutfiles = dir "C:\C:\IBM-Shortcuts\*.hod"
$DestinationDir = "C:\Users\Public\Desktop"
foreach ($shortcutfile in $shortcutfiles ) {
$TargetPath = Get-ChildItem "C:\IBMiACS-Shortcuts" -Filter *.hod -Recurse | % { $_.FullName }
$BaseName = Get-ChildItem "C:\IBMiACS-Shortcuts" -Filter *.hod -Recurse | % { $_.BaseName }
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$DestinationDir" & "$BaseName"+lnk)
$Shortcut.TargetPath = "$TargetPath"
$Shortcut.Save()
}

So a few things to note here:
Use Full cmdlet names instead of alias', this for clarity.
Get-Childitem instead of dir
... | Foreach-Object { ... instead of ... | % ...
Only iterate over the folders contents once and reference the $_ variable within the loop instead of looping within a loop
if a variable is only being used once then don't bother storing it in its own variable
$Destination is no longer used
Indent your code following basic formatting rules
As #mklement0 mentioned, it's worth executing $WshShell = New-Object -comObject WScript.Shell only once, before the pipeline.
$WshShell = New-Object -comObject WScript.Shell
Get-ChildItem "C:\IBM-Shortcuts\*.hod" | Foreach-Object {
$Shortcut = $WshShell.CreateShortcut("C:\Users\Public\Desktop\$($_.BaseName).lnk")
$Shortcut.TargetPath = $_.FullName
$Shortcut.Save()
}
Some other things to note:
On line 1 you are referencing "C:\C:\IBM-Shortcuts\*.hod", this has one too many C:\ in it.
Your use of $TargetPath = Get-ChildItem "C:\IBMiACS-Shortcuts" -Filter *.hod -Recurse | % { $_.FullName } is not setting the targetpath for the current iteration of $Shortcutfile, it is returning a list of all file paths in "C:\IBMiACS-Shortcuts"
Take a look into the basics of a foreach loop here

Related

Powershell script to copy old files and create shortcuts

I am working on creating a stubbing script using powershell. My intentions for this script is to copy any data that hasn't been written to in the past x time period to a new location, check that the file copied, create a shortcut to the "archived" file in it's original location, and eventually delete the old file (I haven't written this section yet). Below is what I have so far. The issue I am having now is that a shortcut is created however all of the shortcuts are saved to the C:\Temp directory and not in any subfolders; if that is where the original file was stored. I think my issue is with $link and I need to split the path's and join them but I am not sure. Any assistance is greatly appreciated!
# Variables
$Original = "C:\Temp"
$Archive = "\\data\users\Temp"
Get-ChildItem -Path $Original -Recurse |
Where-Object {
$_.LastWriteTime -lt [datetime]::Now.AddMinutes(-1)
} | Copy-Item -Destination $Archive -Recurse -force
$sourceFiles = Get-ChildItem $Original -Recurse | Where-Object { $_.LastWriteTime -lt [datetime]::Now.AddMinutes(-1) } | Select Name, Fullname
$destFiles = Get-ChildItem $Archive -Recurse | Select Name, Fullname
$Comparison = Compare-Object $sourceFiles.name $destFiles.name
If ($comparison.sideindicator -ne "<=") {
get-childitem $Archive -Recurse | where { $_.PsIsContainer -eq $false } | ForEach-Object {
$path = '"' + $_.FullName + '"'
$link = $Original + '\' + $_.Basename + '.lnk'
$wshell = New-Object -ComObject WScript.Shell
$shortcut = $wshell.CreateShortcut($link)
$shortcut.TargetPath = $path
$shortcut.Save()
}
}
If ($comparison.sideindicator -eq "<=") {
$comparison.inputobject, $sourceFiles.fullname | Out-File 'C:\ScriptLogs\stubbing.csv' -Force
}
This is the problematic code:
$link = $Original + '\' + $_.Basename + '.lnk'
Basename is only the filename portion without the path, so the .lnk files end up directly in the top-level directory.
You have to use the relative path like this:
$RelativePath = [IO.Path]::GetRelativePath( $Archive, $_.FullName )
$link = [IO.Path]::ChangeExtension( "$Original\$RelativePath", 'lnk' )
This requires .NET 5 for GetRelativePath. To support older .NET versions you can use:
Push-Location $Archive
try { $RelativePath = Resolve-Path -Relative $_.FullName }
finally { Pop-Location }
Resolve-Path -Relative uses the current location as the base base. So we use Push-Location to temporarily change the current location. The try / finally is used to ensure restoring of the original location even in case Resolve-Path throws an exception (e. g. when you have set $ErrorActionPreference = 'Stop').
Although the code might work like this, I think it could be refactored like this:
A single Get-ChildItem call to store the files to be moved into an array (your current $sourceFiles = ... line)
A foreach loop over this array to move each file and create the shortcut.
I think the Compare-Object isn't even necessary, but maybe I'm missing something.
Currently you are iterating over the same directories multiple times, which isn't very efficient and a lot of duplicate code.

How do I get the target from an install program on the desktop or start menu?

I found a snippet on the internet that does this
function Get-DesktopShortcuts{
$Shortcuts = Get-ChildItem -Recurse "C:\users\public\Desktop" -Include *.lnk
$Shell = New-Object -ComObject WScript.Shell
foreach ($Shortcut in $Shortcuts)
{
$Properties = #{
ShortcutName = $Shortcut.Name;
ShortcutFull = $Shortcut.FullName;
ShortcutPath = $shortcut.DirectoryName
Target = $Shell.CreateShortcut($Shortcut).targetpath
}
New-Object PSObject -Property $Properties
}
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
But I am unsure how to manipulate the results.
$output = get-desktopshortcuts stores the results, and I can output it all $output | out-gridview but if Target had foo.exe, I want just the path, C:\Program Files\Foo Enterprises. Since there is no guaranteed way for me to know if the enduser installed it into a non-default location, and of course the shortcut isn't guaranteed, but then i will return the default for worse case scenario.
Thank you!
Once you have the full filename of the file you are interested in, you can use the Split-Path cmdlet to get the directory that the file is located in. For example, if you have a filename with path in $target,
Split-Path -Path $target -Parent
will return the path without the filename, e.g., C:\Windows\System32 if $target happened to be C:\Windows\System32\Notepad.exe.
You can find out more on Split-Path at Microsoft Docs.
Using the function I already had above, I played around and found a rather elegant way, which works.
$sc = Get-DesktopShortcuts
$target = $sc."Target" | select-string -pattern "FOO"
split-path -parent $target
Yes, it does assume the shortcut exists, but in the end, it answered what I asked.

Why shortcuts are not created?

i have trouble in powershell (Windows 10 1809)
I want that this script seach all .exe files in my directory D:\Test in Recursive mode and make shortcuts in D:\ . But my code just creating link.lnk in D:\ . Why?
$1 = get-childitem "D:\Test" -recurse | where {$_.extension -eq ".exe"} | % {
Write-Host $_.FullName
}
ForEach ($item in $1) {
$Shell = New-Object -ComObject ("WScript.Shell")
$ShortCut = $Shell.CreateShortcut("D:\link.lnk")
$ShortCut.TargetPath= $1
$Shortcut.Save()
}
It looks like you are naming all of the shortcuts D:\link.lnk, and the loop will just overwrite the same name over and over. Specifying a unique shortcut link filename using the BaseName property (name minus the extension) should solve your problem. Also you need to use the loop $item to specify the TargetPath.
Also, the by looping through with the Write-Host you don't actually assign anything to your variable $1. By removing the % { Write-Host $_.FullName } $1 does get the proper output. So the proper code should be:
$1 = get-childitem "D:\Test" -recurse | where {$_.extension -eq ".exe"}
ForEach ($item in $1) {
Write-Host "Creating Shortcut: D:\$($item.BaseName).lnk Pointing to: $($item.FullName)"
$Shell = New-Object -ComObject ("WScript.Shell")
$ShortCut = $Shell.CreateShortcut("D:\$($item.BaseName).lnk")
$ShortCut.TargetPath= $item.FullName
$Shortcut.Save()
}

Powershell: Edit the drive letter of all my shortcuts that begin with X:\

I have a situation where all the paths in the shortcut files that are located in the %AppData%\Microsoft\Windows\Start Menu\Programs folder and subfolders all point to an incorrect drive letter. This includes the Target: value, Start In: value and all the paths to the icon files as well. I'd like to change them all from X:\ to C:\
There are a couple that are correctly pointing to C:\ but there's only a handful of them.
Here is the code I was working with. I am able to change the TargetPath but not the WorkingDirectory value. I've tried removing the comment on line 20 but that creates an error about $null-valued expression. I've also tried duplicating the bit for TargetPath to WorkingDirectory but it does not change:
$folder = "C:\Temp\Shortcuts"
[string]$from = "X:\"
[string]$to = "C:\"
$list = Get-ChildItem -Path $folder -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($from.tostring(),$to.ToString())
# [string]$path = $link.WorkingDirectory
# [string]$path = [string]$path.Replace($from.tostring(),$to.ToString())
#If you need workingdirectory change please uncomment the below line.
#$link.WorkingDirectory = [string]$WorkingDirectory.Replace($from.tostring(),$to.ToString())
$link.TargetPath = [string]$path
$link.Save()
}
Line 20 in the code you posted is the trailing }, but I'm assuming this...
#$link.WorkingDirectory = [string]$WorkingDirectory.Replace($from.tostring(),$to.ToString())
...is the real line 20. The reason for that error is because you are trying to call .Replace() on $WorkingDirectory instead of $link.WorkingDirectory. $WorkingDirectory, if it's not set anywhere, will evaluate to $null.
With that corrected, PowerShell provides its own string replacement operators: -replace and -ireplace are case-insensitive, -creplace is case-sensitive. The first operand to all of these operators is a regular expression, and since a backslash in regex denotes a special character, you will need to escape the \ in your search pattern like this...
[string]$from = "X:\\"
You can then change the drive letter of the WorkingDirectory property with...
$link.WorkingDirectory = [string] $link.WorkingDirectory -replace $from.tostring(),$to.ToString()
...or...
$link.WorkingDirectory = [string] $link.WorkingDirectory -ireplace $from.tostring(),$to.ToString()
Note that $link.WorkingDirectory, $from, and $to are already of type [String], so the [String] cast and calls to .ToString() are unnecessary and can be removed...
$link.WorkingDirectory = $link.WorkingDirectory -replace $from,$to
One tiny optimization you might make is to add an anchor to your search pattern so it won't bother to search for the drive letter beyond the absolute beginning of the [String]...
[string]$from = "^X:\\"
Also, since you are using PowerShell 3+, instead of filtering out directories like this...
$list = Get-ChildItem -Path $folder -Recurse | Where-Object { $_.Attributes -ne "Directory"}
...you can filter in files like this...
$list = Get-ChildItem -Path $folder -Recurse -File
Better yet, you can use the -Filter parameter to only include files with an .lnk extension, too...
$list = Get-ChildItem -Path $folder -Recurse -File -Filter '*.lnk'

Using powershell to batch convert docx to pdf

I'm attempting to use powershell to batch convert a lot of docx into pdf, into a different directory while maintaining the folder structure of the root.
I have the script working, however around 1 out of every 10 documents word pops up a "SaveAs" dialog, which i do not understand prompting me to save the docx file, although i have visible set to false.
#Stage the files
$sourceDir = "C:\Documents\"
$targetDir = "C:\Temp_Stage\"
Get-ChildItem $sourceDir -filter "*.doc?" -recurse | foreach{
$targetFile = $targetDir + $_.FullName.SubString($sourceDir.Length);
New-Item -ItemType File -Path $targetFile -Force;
Copy-Item $_.FullName -destination $targetFile
}
#Convert the files
$wdFormatPDF = 17
$word = New-Object -ComObject word.application
$word.visible = $false
$folderpath = "c:\Temp_Stage\*"
$fileTypes = "*.docx","*doc"
Get-ChildItem -path $folderpath -include $fileTypes -Recurse |
foreach-object {
$path = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
$doc = $word.documents.open($_.fullname)
$doc.saveas([ref] $path, [ref]$wdFormatPDF)
$doc.close()
}
$word.Quit()
Is there a way to suppress all word dialogs / warning / errors it should be a fairly automatic process that has ended up being pretty manual process.
I found out that you should pause between the Word-COM commands.
I also had to write a script that Word converts the documents from. dot to. dotm.
Not only did I occasionally get the save dialog, but also a lot of E_FAIL errors in the console.
The breaks (maximum 50ms) helped a lot.
Break in Powershell:
Start-Sleep -m 50
I hope it will help you.
Greetings
Bloodrayne1995