Generating printer shortcuts from list - powershell

I am trying to generate a shortcut for every printer I have on a print server. The idea is to be able to email these shortcuts to people and when they click on them, it automatically installs that printer for them.
I've populated an array from a list of printer names exported from the print server:
$list = #((get-contnet $home\dekstop\plist.txt))
I then created a method to create a shortcut:
function Make-Shortcut
{
param ([string]$dest, [string]$source)
$WshShell = New-Object -comObject Wscript.Shell
$Shortcut = $WshShell.CreateShortcut($dest)
$Shortcut.TargetPath = $Source
$Shortcut.Save()
}
The function works fine. I was able to create standard shortcuts with no problem.
This next part is where I am getting stuck:
foreach ($i in $list)
{
Make-Shortcut "C:\pshort\$i.lnk" "C:\Windows\System32\rundll32.exe
printui.dll,PrintUIEntry /in /q /n\\printserver\$i"
}
When this runs, it does generate a shortcut with the same name as the printer for each printer on the list. However, the problem comes in at the target path. Instead of
C:\Windows\System32\rundll32.exe printui.dll,PrintUIEntry /in /q /n\\printserver\printername
it changes it to:
C:\Windows\System32\rundll32.exe printui.dll,PrintUIEntry \in \q \n\printserver\printername
The three problems with this are:
It is reversing the forward slash for the parameters
It is removing one of the backslashes preceding the server name
It is adding quotes to both sides. I need the quotes to come off for the shortcut to work properly.
I assume this is happening because Powershell thinks I am trying to make a standard shortcut and thinks I made mistakes while typing out the path.
I have tried putting a ` in front of each forward slash hoping the escape character would prevent it from reversing it, but no luck. I also tried using a hyphen for each parameter but that did not work either.
Is there anyway to stop this from happening? Or is there perhaps a better way to try to accomplish what I am trying to do?

You need to add arguments to the com object
Try adding a new param $arguments to your Make-Shortcut function and do:
Make-Shortcut "C:\pshort\$i.lnk" "C:\Windows\System32\rundll32.exe"
"printui.dll,PrintUIEntry /in /q /n\\printserver\$i"
add this in your function:
$Shortcut.Arguments = $arguments
So the link is created successfully ... but I have no idea if it works :)

Completely different answer but in a standard windows environment simply clicking a hyperlink to \printserver\printer will add a shared printer to someone's system?
So an email that simply lists :
\\PrintServer\Printer01
\\PrintServer\Printer02
\\PrintServer\Printer03
Would probably do the job just as well.

Related

Changing the caption permanently

In my script, I start a third-party non-GUI application. I'm sort of trying to run this embedded in my script itself, so I will be able to change the icon and the windows caption.
I have two restriction:
I have to use & 'application.exe' to start the application. I tested Start-Process -NoNewWindow, but that breaks the functionality of application.exe.
The application.exe needs to be running in my script. I can only change the icon when I compile my script with PS1 to Exe afterwards.
The challenge I'm now facing is related to the first restriction. I need to change the caption a-synchronously. The $host.ui.RawUI.WindowTitle = “New Title” is not working, because application.exe changes the caption right after execution. So I need to change it by using functions like SetWindowText(). This is working in VB.NET, but I'm looking for a way to start this function in parallel with the & 'application.exe'. When I use &, the application is executed and the script waits until it terminates. So I need to do the SetWindowText() in parallel.
Visual Basic/C has a BackgroundWorker functions for such cases. Is something like that also available in PowerShell?
Thanks for any help in advance!
Kind regards,
Eric
Everybody thank you very much for your help!
The solution proved to be a lot easier that I thought. You don't have to keep on renaming the window. You just have to start the cmd window, wait a bit (in the background it's doing something with conhost.exe) and then rename it once. Here's the code I used:
$titletext = "My New CMD Window Title"
# Start a thread job to change the window title to $titletext
$null = Start-ThreadJob { param( $rawUI, $windowTitle )
Start-Sleep -s 2 #Wait until cmd.exe is started
if ( $rawUI.WindowTitle -ne $windowTitle ) {
$rawUI.WindowTitle = $windowTitle
}
} -ArgumentList $host.ui.RawUI, $titletext
& 'c:\windows\system32\cmd.exe'
Kind regards,
Eric

Powershell issues with Start-Process of same script elevated

I have a script that inventories the installed software on a machine, based on certain criteria provided in arguments. It also needs to support being buried in a very deep folder structure (Architects just think like that and I need to support it, not control it). And since it now also supports AppX, which requires running elevated, which in turn requires the full path to the PS1 in the -file argument, I am running into issues with the overall length of the Target value in the shortcut.
So, I was looking at the usual run with the -verb argument approach, like this
If (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-verb', 'Runas', '-file',$PSCommandPath)
$pxArguments = #($myInvocation.BoundParameters.GetEnumerator().ForEach({"-$($_.Key)", "$($_.Value)"}))
$arguments = $psArguments + $pxArguments
Start-Process 'powershell.exe' -ArgumentList:$arguments -Verb:Runas
Stop-Process $PID
} else {
Write-Host 'Elevated'
# actual code to process
}
But I am having two issues there, one is critical, one less so.
On the critical side, I see a second blue PowerShell console open for a split second, but then it closes with no error or anything. I would have expected the -noExit argument in $psArguments to at least leave that window open for me, so I could read any errors. But I can't see any red, so that makes me think there is no error, and this usually happens when the -file argument doesn't point to an actual script. However, if I validate the arguments like so
foreach ($item in $arguments) {
Write-Host "$item"
}
Write-Host "$PSCommandPath $(Test-Path $PSCommandPath)"
The arguments all look good and Test-Path returns true.
So, what in this am I getting wrong, such that I don't get a new PowerShell console that actually works?
The second issue is that I have defined fonts and colors and console size in the shortcut, and I wonder if there is a single variable, perhaps $host, that I could then send to the second session populated with the values of the first. I suspect not, because I think the only types that can be used as arguments are strings and numbers.
DOH: I was on the right track, with that comment about it looking like the script wasn't found. There are SPACES in that path, so I need
$psArguments = #('-nologo', '-noExit', '-executionpolicy', 'bypass', '-file',"`"$PSCommandPath`"")
Now I just need to deal with resizing the console. I can live without fonts and colors, but the width and text wrapping I need to address.

Start process using wildcard for drive letter

I have a file which will always be located on a users desktop, however, depending on how the machine was set up, the desktop could be located on the C drive or D drive.
Below is what I have tried to use. On it's own, (Get-Location).Drive.Name will return the C drive on my machine. However, if I combine it into the full line of code to start the program, it does not work.
What am I doing wrong?
$userName = [Environment]::UserName
Start-Process ((Get-Location).Drive.Name)\Users\$($userName)\Desktop\VIPHorizon\Service_Desk.url
While the other answer is correct that you are missing a colon, simply adding that is not sufficient.
If you try running it with the colon, you will get another error like so:
Unexpected token ':\Users' in expression or statement.
In order to fix this, you need to wrap the whole thing in quote marks.
"((get-location).Drive.Name):\Users\$($userName)\Desktop\VIPHorizon\Service_Desk.url"
Then you will find that the ((get-location).Drive.Name) is written directly into the string, instead of being replaced. which can be fixed by prefixing the call with a dollar sign like you have already done for userName.
"$((get-location).Drive.Name):\Users\$($userName)\Desktop\VIPHorizon\Service_Desk.url"
You need to put a colon ( : ) right after your drive letter:
$userName = [Environment]::UserName
Start-Process ((get-location).Drive.Name):\Users\$($userName)\Desktop\VIPHorizon\Service_Desk.url
The proper way to get current users Desktop even if relocated is:
[Environment]::GetFolderPath('Desktop')
So use:
Start-Process (Join-Path [Environment]::GetFolderPath('Desktop') "VIPHorizon\Service_Desk.url")
To eumerate all special folders and current settings:
$SpecialFolders = [Environment+SpecialFolder]::GetNames([Environment+SpecialFolder])|Sort
ForEach ($SpecialFolder in $SpecialFolders) {
[PSCustomObject]#{
SpecialFolderName = $SpecialFolder
SpecialFolderLocation = ([Environment]::GetFolderPath($SpecialFolder))
}
}
The global Powershell variable $HOME will give you the home location for the active user.
Start-Process $HOME\Desktop\VIPHorizon\Service_Desk.url

Get target of shortcut (.lnk) file with powershell

I have a bunch of .lnk files and need to treat them differently depending on the target that the shortcut points to. I've found very little of how to this with other languages, but nothing about doing this with powershell.
I've tried this:
$sh = New-Object -COM WScript.Shell
$target = $sh.CreateShortcut('<path>').Target
But this returns an empty string even though I can see in the .lnk properties that the Target is specified.
Any idea on how to accomplish this?
You have made an error in the property; as wOxxOm suggests, you should be using TargetPath rather than Target:
$sh = New-Object -ComObject WScript.Shell
$target = $sh.CreateShortcut('<full-path-to-shortcut>').TargetPath
Google and MSDN were indeed helpful here; additionally, piping objects to Get-Member can often be useful and educational. This question also shows how to manipulate shortcuts using PowerShell, and uses the same technique as seen here.
If you want the arguments to the executable as well, those are stored separately:
$arguments = $sh.CreateShortcut('<full-path-to-shortcut>').Arguments
Again, piping objects to Get-Member - in this case, the object returned by WScript.Shell.CreateShortcut() - provides useful information.
It should be noted that there are issues with using this technique and these calls when the path contains Unicode emoji characters; there is a workaround for this case in this StackOverflow question.
It may seem obvious to experts but to us simpletons there seems to be a key lightbulb moment here:
<full-path-to-shortcut> = the Full Name! Doh!
Make sure you use .FullName if you use Get_ChildItem | ForEach-Object, etc. with the Shell .CreateShortcut call if you want the current target of a shortcut. For me:
.CreateShortcut($_.FullName) returned an appropriate value; whereas
.CreateShortcut($_) returned 'null'

Powershell: Passing command options forward slash changing to backward

I am trying to use the invoke (ii) command to open an access database that has command line options. What I would like to have executed is below (yes there is a space in the name of the access database). The database is in the same folder as the Powershell script.
What I want: program name.accdb /cmd Rester
What I get: program name.accdb \cmd Rester
The exact commands I am using are:
$Path_To_EXE = "program name.accdb /cmd Rester"
&ii $Path_To_EXE
I am new to Powershell and have done some searching but can't seem to find an answer. I can create a work around by creating a separate .bat file but that seems like going backwards.
Thoughts?
You should also give a shot to the start-process cmdlet :
$Path_To_EXE = "c:\program.exe"
#Notice the simple quotes ...
$Arguments = #( "name.accdb", '/cmd' , "Rester" )
start-process -FilePath $Path_To_EXE -ArgumentList $Arguments -Wait
I'm not quite sure of the format of the answer you'll get tough ...
for database interaction, I'll rather use JGreenwell's Approach, since the answer that you'll get will be much easier to read/debug ...
Let me know if it works.
If you want to run a VBA script while passing it a parameter with powershell:
$aApp = New-Object -ComObject access.application
$aApp.Application.OpenCurrentDatabase("some program.accdb")
$aApp.Application.Run("VBAScriptName", [ref] "Raster")
First according to Microsoft Support you can use ;; for /cmd from the command line. Second because of the way call quotes and dequotes variables you have to include the /cmd flag separate from the variable (well, its the easiest way). Third, you might consider creating a new com-object to handle running Access with Powershell as it allows for a lot more options (just ask and I can add some examples of this). This being said try:
$Path_To_EXE = "program name.accdb"
&ii $Path_To_EXE ;;Rester #Try ;;"Rester" if it doesn't work.
#if that works then its a problem in Rester
#fyi another way is:
$Path_To_EXE = #("program name.accdb", ";;Rester")
&ii $Path_To_EXE
If you want to use an ActiveX Object Controller to open and perform operations on Access look at this blog from technet <- Read the link there are pitfalls to avoid.
$adOpenStatic = 3
$adLockOptimistic = 3
$objConnection = New-Object -com "ADODB.Connection"
$objRecordSet = New-Object -com "ADODB.Recordset"
$objConnection.Open("Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\Scripts\Test.mdb")
$objRecordset.Open("Select * From Computers", $objConnection,$adOpenStatic,$adLockOptimistic)
$objRecordSet.AddNew()
$objRecordSet.Fields.Item("ComputerName").Value = "atl-ws-001"
$objRecordSet.Fields.Item("SerialNumber").Value = "192ATG43R"
$objRecordSet.Update()
$objRecordSet.Close()
$objConnection.Close()