Add-Printer -ConnectionName \\printserver\printername - powershell

Add-Printer -ConnectionName \\printserver\printername
With printserver and printername replaced with my companies actual server and printer name, I get the error:
Add-Printer: The specified server does not exist, or the server or printer name is invalid. Names may not contain ',' or '\' characters.
Seems pretty straight-forward, I cannot use \ under the parameter ConnectionName. But this code was taken directly from Microsofts own documentation, so to me it's very weird that it doesn't work.
Have I missed something trivial or is there some bigger step I don't know about?

I use the following method which I've used against Windows 8.1 Enterprise and Windows 10 Enterprise domain workstations.
To add a printer:
Invoke-Command -ComputerName $computer -Scriptblock {RUNDLL32 PRINTUI.DLL,PrintUIEntry /ga /n\\PrintServer\ShareName }
To remove a printer:
Invoke-Command -ComputerName $computer -Scriptblock {RUNDLL32 PRINTUI.DLL,PrintUIEntry /gd /n\\PrintServer\ShareName }
More information: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/rundll32-printui

Related

How to remotely deploy multiple PowerShell cmdlets/scripts to multiple remote devices?

I'm looking to use a single host server to maintain a PowerShell script, with global variables, that can be interpreted and ran on several other devices in the network cluster.
On the main host I'd like to specifically be able to maintain a list of variables for IP addresses of each other device that I want to run the scripts against, but then how I want to run the script is something I'm having a hard time determining. There are several things I need to do to each other machine in the cluster (change the computer name, modify the time zone and time, configure the network adapters.... there's a decent list of stuff). The commandlets to do the functions on the individual machines is no problem... I have all of that written out and tested. I just don't what my options are for where that script is stored. Preferably, I think I'd like to declare all of the variables for everything that needs to be done on all machines, at the top of the file on the main host. Then I would like to break down everything that needs to be done to each host on the same file, on the main host. I know it will get a little messy, but that would make maintaining the cmdlets for each device much easier, especially when it comes to testing and making changes. Am I trying to do the impossible here??
I learned about using ENABLE-PSSESSION as well as INVOKE-COMMAND, but each seem to have their own challenges. With Enable-PSSession I cannot seem to find a way to wait for the script to connect to each host before it moves on to the next line. I've tried piping in Out-Null, as well as adding a Start-Sleep line. I don't want to have to manually connect to each host and then manually run the list of commands against each host. Invoke-Command doesn't seem to let me break out the SCRIPTBLOCK section into multiple lines.
Is there any suggestion for the best method of accomplishing the desire to run the script from the main host, that performs all of my cmdlets on multiple machines, without any additional human interaction??
Thanks so much!!
-Andrew
EDIT: I found that I can break the ScriptBlock line (contrary to what I thought didn't work yesterday). Here is basically what I'm trying to accomplish, though of course the below does not work when calling the variables from the top of the file:
#Edit These Variables
$NewName_Server2 = "Server2"
$NewName_Server3 = "Server3"
$NewName_Server4 = "Server4"
$IPAddress_Server2 = "10.10.10.2"
$IPAddress_Server3 = "10.10.10.3"
$IPAddress_Server4 = "10.10.10.4"
$TimeZone = "US Eastern Standard Time"
#Do Not Edit These Variables
$Server2 = "192.168.1.2"
$Server3 = "192.168.1.3"
$Server4 = "192.168.1.4"
#Configure Server 2
Invoke-Command -ComputerName $Server2 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server2
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server2
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 3
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server3
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
#Configure Server 4
Invoke-Command -ComputerName $Server3 -ArgumentList $local -ScriptBlock {
Rename-Computer -NewName $NewName_Server3
New-NetIPAddress -InterfaceAlias "Wired Ethernet Connection" -IPv4Address $IPAddress_Server4
Set-TimeZone -ID $TimeZone
Restart-Computer -Force
}
You can use the using scope to access local variables. I don't know what $local is. Nice try.
$a = 'hi'
invoke-command comp001,comp002 { $using:a }
hi
hi
The other way is using a param, not well documented. Passing arrays is more tricky.
invoke-command comp001,comp002 { param($b) $b } -args $a

PowerShell failing to copy from UNC path

I'm working on a script to copy a folder from a UNC path to a local server. I'm remotely running my script through an interactive session and utilizing Invoke-Command -ScriptBlock like so:
Invoke-Command -ComputerName MyServer -ScriptBlock $Script
This is the script to do the copying:
$script {
try {
New-PSDrive -Name MyDrive -PSProvider FileSystem -Root \\uncpathserver\e$\SourceCode\ -Credential Contoso\me
Copy-Item -Path \\uncpathserver\e$\SourceCode\* -Destination E:\Inetpub\Target -Recurse -Force
}
catch {
Write-Host "Failed to copy!"
}
}
It is failing and throwing my catch block every time. I can't seem to figure out what I am missing to get this to work - it seems so simple and I hope I'm not missing something blatantly obvious.
EDIT:
I was able to get it to work by now just running the script from my local PC instead of from a server. I'm calling the file copy out of $script block now as well. This is what the new code looks like:
$MyServers= #("server-01", "server-02")
foreach ($server in $MyServers)
{
$TargetSession = New-PSSession -ComputerName $server -Credential
contoso\me
Copy-Item -ToSession $TargetSession -Path C:\Source\TheCode\ -
Destination "E:\InetPub\wherethecodegoes" -Recurse -Force
}
Everything else I'm doing inside my $script block (which has been omitted here for troubleshooting sake) is working A-OK. I do have to enter my credentials for each server, but due to the small nature of servers I'm working with, that isn't a deal breaker.
Sounds like a 'kerberos double hop' problem.
Short-Answer
Avoid the problem. From your system, setup two PSdrives. Then copy \\uncpathserver\e$\SourceCode\ to \\RemoteIISserver\E$\Inetpub\Target\
Long-Answer
From your system (System A), you are remotely executing a script (on System B) that will copy a remote folder (from System C).
It should work, but it doesn't. This is because when you (specifically, your account) from System A, remotely connects to System B, then asks System C for something, 'System C' doesn't trust you.
A quick google of the problem will show a myriad of ways around this issue, however;
Not all methods are secure (example: CredSSP)
Not all methods will work on your version of Windows (which is...?)
Not all methods will work with PowerShell
One secure method that does work with PowerShell leverages delegation.
This can be a bit daunting to setup, and I suggest you read-up on this thoroughly.
## Module 'ActiveDirectory' from RSAT-AD-PowerShell Windows feature required.
$ServerA = $Dnv:COMPUTERNAME
$ServerB = Get-ADComputer -Identity ServerB
$ServerC = Get-ADComputer -Identity ServerC
Delegate 'Server B' to access 'Server C';
# Set the resource-based Kerberos constrained delegation
Set-ADComputer -Identity $ServerC -PrincipalsAllowedToDelegateToAccount $ServerB
# Confirm AllowedToActOnBehalfOfOtherIdentity.Access is correct (indirectly).
Get-ADComputer -Identity $ServerC -Properties PrincipalsAllowedToDelegateToAccount
Wait about 15 minutes for 'Server B' to sync-up (or just reboot it).
You can force this with the following (Note: $Cred should contain your credentials);
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
klist purge -li 0x3e7
}
Run a test-hop;
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
Test-Path \\$($using:ServerC.Name)\C$
Get-Process lsass -ComputerName $($using:ServerC.Name)
Get-EventLog -LogName System -Newest 3 -ComputerName $($using:ServerC.Name)
}
The downside is you have to setup every remote remote-target (every 'Server C') this way. But the upside is that it's secure.

Add-Printer with Network Printer fails

When I use Add-Printer -ConnectionName '\\server\Printer' from a local PowerShell environment it succeeds.
When I use the same command, but wrapped in an Invoke-Command like so:
Invoke-Command -ComputerName 'someServer' -Credential $creds -Authentication CredSSP -ScriptBlock { Add-Printer -ConnectionName '\\server\Printer' }
it fails saying
'\' is an invalid character.
I'm guessing it's connected to different levels of user profile being available locally vs remotely, but I'm not sure how to resolve it.
Edit: To update this, I've had to abandon this approach as I couldn't get it to work. My work-around has been to use
RUNDLL32 PRINTUI.DLL,PrintUIEntry /ga /c\\computerName /n\\someServer\Printer /q
Which works, though it does something slightly different to the Add-Printer cmdlet.
I'm not 100% sure why you're seeing that error. I'm sure it'll be something to do with how it's handling passing through the string.
However, you shouldn't really need to run it through Invoke-Command as Add-Printer has two options within itself to add printers to remote computers.
You can specify single machine with the -CompuerName parameter:
Add-Printer -ComputerName 'someServer' -ConnectionName '\\server\Printer'
You can also specify a (or multiple) CimSessions with the -CimSession parameter, allowing you to hit a bunch of machines at once.
A caveat with this command to be aware of is that it only works on Server 2012/ Windows 8 and above (including the remote target).
I ran into a similar problem, but I was trying to loop through a list of shared printers from a print server then add all of them to the local machine. Jump to the bottom if you want to see my solution.
The following returns all of the shared printers as objects:
$printerList = Get-Printer -ComputerName PrintServer | where Shared -eq $true
The following should have looped through all my printers and added each one:
foreach ($printer in $printerList) {
Add-Printer -ConnectionName "\\PrintServer\$printer.SharedName"
}
Instead, I supposedly run into the same error as the OP:
Add-Printer : The specified server does not exist, or the server or printer name is invalid. Names may not contain ',' or '\' characters.
At line:1 char:38
+ ... nterList) { Add-Printer -ConnectionName "\PrintServer\$printer.SharedNam ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (MSFT_Printer:ROOT/StandardCimv2/MSFT_Printer) [Add-Printer], CimException
+ FullyQualifiedErrorId : HRESULT 0x80070709,Add-Printer
Needless to say, I'm very puzzled by those results because when I test what is going on, I see the following:
Write-Host $printer.ShareName
printerName
Great! Why doesn't it work in double quotes though? I am intentionally using double quotes to return the contents of the $printer variable:
Write-Host "$printer.ShareName"
MSFT_Printer (Name = "printerName", ComputerName = "PrintServer", DeviceType = 0, Type = 0).ShareName
Rather than mess with a CimException likely due to typecasting or who knows what (and yes, the ShareName property is a simple string already, it's not a hash table or a nested funky datatype of sorts), I did a simple ToString() as a workaround:
$printerList = Get-Printer -ComputerName PrintServer | where Shared -eq $true
foreach ($printer in $printerList) {
$printerStr = $printer.ShareName.ToString()
Add-Printer -ConnectionName "\\PrintServer\$printerStr"
}
I ran into this issue as well ToString() didn't resolve it for me. It was the "not required" parameter -ComputerName. I was running remove-PrinterPort -Name $port, which resulted in the following error:
The specified server does not exist, or the server or printer name is invalid. Names may not contain ',' or '\' characters.
After banging my head, and Googlin', I finally added the computer name as a test like this $env:computername, I tried using a "." before, because I thought that meant local computer but, it didn't work, just stalled.
After adding the computer name, the ports, and drivers were removed without error.
get-help Remove-PrinterPort shows that -Computer name is not required but, it does seem to be required:
-ComputerName [<String>]
Specifies the name of the computer from which to remove the printer port.
Required? false
Position? named
Default value none
Accept pipeline input? false
Accept wildcard characters? false

Invoke-command and msiexec

I'm trying to remove an application on a remote machine using the Invoke-Command cmdlet but it's not working.
Here is my script:
Invoke-Command -ComputerName "Computername" -Verbose -ScriptBlock {
msiexec.exe /x '{4ADBF5BE-7CAF-4193-A1F9-AM6820E68569}' /qn /passive
}
Are there any reliable, working alternatives in this context?
This doesn't use Invoke-Command or MSIExec, but it's a functional uninstall method for removing applications on remote machines using WMI for anything registered with WMI (should be anything installed via msiexec).
(Get-WmiObject -Class Win32_product -ComputerName ComputerName -Filter {IdentifyingNumber LIKE '{4ADBF5BE-7CAF-4193-A1F9-AM6820E68569}'}).uninstall()
Additionally that can be put into a ForEach loop if you have several computers to do it on. If you have the Name, IdentifyingNumber, and Version listed in WMI you can make it much faster with the following context (using AT&T Connect Participant Application v9.0.82):
$App="IdentifyingNumber=`"`{1F3A6960-8470-4C84-820C-EBFFAF4DA580`}`",Name=`"AT&T Connect Participant Application v9.0.82`",version=`"9.0.82`""
([WMI]\\ComputerName\root\cimv2:Win32_Product.$App).Uninstall()
Yes, the $App string is horribly escaped, but that's due to the way WMI requires the string to be formatted with curly braces and double quotes and what not. This is not exactly useful for a single uninstall since it requires you to get all that info up front and format the key string. If you were going to remove a piece of software off 30 machines though, it would be much better. You can get all that info by just leaving off the .Uninstall() method from my first command, so...
Get-WmiObject -Class Win32_product -ComputerName RemoteComputer -Filter {IdentifyingNumber LIKE '{1F3A6960-8470-4C84-820C-EBFFAF4DA580}'}
Will spit back something like:
IdentifyingNumber : {1F3A6960-8470-4C84-820C-EBFFAF4DA580}
Name : AT&T Connect Participant Application v9.0.82
Vendor : AT&T Inc.
Version : 9.0.82
Caption : AT&T Connect Participant Application v9.0.82
Can also be used with the name, or even partial names by changing the filter to something like `{Name LIKE '%AT&T Connect%'} or you can query WMI to list all the applications registered with it by leaving the -Filter off completely, though you probably want to pipe that to Format-Table to make it readable. I used:
gwmi -class win32_product -computername RemoteComputer|ft IdentifyingNumber,Name,Version
A good read with more info about this can be found at this link
Here is the solution I came up with
$myses = New-PSSession -ComputerName "Computer"
Invoke-Command -Session $myses -ScriptBlock {
#finds all instances of Java installed
$find_sep = gwmi win32_product -filter "Name LIKE '%Java%'" | select -ExpandProperty IdentifyingNumber
foreach($i in $find_sep){
msiexec.exe /x $i /qn /passive /l*v! c:\uninst.log
}
}

Installing Windows Features on remote server 2012 using powershell 3.0

I am wondering which is best practice considering both examples will probably work. Using the built in help examples I have written a script to install windows features on remote servers. Here is my code:
$servers = ('server1', 'server2', 'server3', 'server4')
ForEach ($server in $servers) {
Install-WindowsFeature -Name Desktop-Experience -ComputerName $server -IncludeAllSubFeature -IncludeManagementTools -Restart
}
Would the above be preferred OR should I wrap the "Install-WindowsFeature ..." in an "Invoke-Command" block like the following?
Invoke-Command -ComputerName server1, server2, server3, server4 -command {
Install-WindowsFeature -Name Desktop-Experience -ComputerName $server -IncludeAllSubFeature -IncludeManagementTools -Restart
}
Thanks for your insight!
Personally I would use the latter (directly call Install-WindowsFeature -ComputerName $server rather than do a separate Invoke-Command) in this case for the following reasons:
You may be hard-coding the feature names now, but in the future you may want to put those in a variable. If you put them in a variable, you'll have to pass it as a parameter into the Invoke-Command's script block. This is entirely possible, but more work.
By using your own loop, you can write progress messages, logging, etc.
You gain nothing by using Invoke-Command in this case because you're running a single command on the remote computer (as opposed to running multiple commands with -ComputerName parameters vs. running multiple commands inside the script block).