Add-Printer with Network Printer fails - powershell

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

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

Trying to Retrieve A Reg Value From Remote Machines Using Powershell

I am trying to retrieve a registry value from each computer using a for each loop and then output that value to a folder in a csv.
That part works fine. The part I am having an issue with is having powershell connect to the remote computers.
This is running internal only
I have admin rights across all workstations
Firewalls are configured to allow all traffic to pass
When I run this script I get this error for every workstation it tried to connect to:
Enter-PSSession : Connecting to remote server workstationX failed with
the following error message : WinRM cannot complete the operation.
Verify that the specified computer name is valid, that the computer
is accessible over the network, and that a firewall exception for the
WinRM service is enabled and allows access from this computer. By
default, the WinRM firewall exception for public profiles limits
access to remote computers within the same local subnet. For more
information, see the about_Remote_Troubleshooting Help topic. At
C:\Users\Rich_Ellis\Desktop\O365\O365Channels\O365Channel.ps1:5 char:2
+ {Enter-PSSession -ComputerName $Computer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (workstationX:String) [Enter-PSSession], PSRemotingTransportException
+ FullyQualifiedErrorId : CreateRemoteRunspaceFailed
My script is:
$Computers = Get-Content "C:\Users\Rich_Ellis\Desktop\O365\O365Channels\computers.txt"
foreach ($Computer in $Computers)
{Enter-PSSession -ComputerName $Computer
$key = 'HKLM:\SOFTWARE\Microsoft\Office\CLickToRun\Configuration'
(Get-ItemProperty -Path $key -Name CDNBaseUrl).CDNBaseUrl | Export-CSV -path "\\s00itstorage\OfficeChannel\$($env:COMPUTERNAME)-O365Channel03292018.csv"}
Any help would be appreciated. TIA
Molding a previous answer to your use-case:
$HKEY_LOCAL_MACHINE = 2147483650
$GwmiArgs = #{
Class = 'StdRegProv'
Namespace = 'Root\Default'
List = $True
}
ForEach ($Computer in #(Get-Content -Path 'C:\Users\Rich_Ellis\Desktop\O365\O365Channels\computers.txt'))
{
$GwmiArgs['ComputerName'] = $Computer
$Registry = Get-WmiObject #GwmiArgs
$Registry.GetStringValue(
$HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Office\ClickToRun\Configuration',
'CDNBaseUrl'
).sValue | Export-CSV -Path "\\s00itstorage\OfficeChannel\$Computer-O365Channel03292018.csv"
}
This uses wmi instead of psremoting to poll the information which may be easier to rely on as it is already configured/enabled on most PCs and can utilize IP addresses due to DCOM/RPC (psremoting only supports kerberos by default)
This solution can be further improved by using Invoke-WmiMethod instead of creating a wmi object for each poll, but I haven't done the work already for that!

Add-Printer -ConnectionName \\printserver\printername

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

How to remotely change a service account password on multiple servers

I've been searching through the archives here but haven't quite found a simple (ie: something I can understand) solution to my problem. I am changing passwords on service accounts using powershell and wmi. I can change the services one at a time across all servers like so:
$Service = Get-WmiObject -Class Win32_Service -computer REMOTESERVER -filter "name='SERVICENAME'"
$service.change($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
As you can see, I can refer to whatever server I want and whatever service on that server. What I'd like to do is the following
Provide a list of servers (a text file with "REMOTESERVER1,REMOTESERVER2" or something similar
Change the password for multiple services on the same machine that are running under the same credentials. I was able to get a list of mutliple services using -filter "StartName LIKE '%\MYSERVICEACCOUNT'", but when I then try to run the $service.change to update the password, I get an error
Method invocation failed because [System.Object[]] doesn't contain a method named 'change'.
At line:1 char:16
+ $service.change <<<< ($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
+ CategoryInfo : InvalidOperation: (change:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
It works fine with only one service though
Restart services only if they are already running
How can I enhance this script to handle these 3 additional items?
Thank you
You would want to add a loop to your above script, and you would want
param
(
[string]$File
)
$Computer = Get-Content $file
foreach ($i in $Computer){
$Service = Get-WmiObject -Class Win32_Service -computer $i -filter "name='SERVICENAME'"
$service.change($null,$null,$null,$null,$null,$null,$null,"newpasswordhere")
}
You add the Param so you can utilize this many different times, then the foreach loop will run through your code for each computer in your .txt file.
Now this only answers the first part of your question but should give you a good starting point.

Powershell Invoke-Command Operations Error

I'm stumped by this issue.
I've written a powershell script which I'm trying to use to import a GPO across multiple domains and then link it with new-gplink. I've made sure all servers have GP Powershell module installed and it's been working pretty well so far, however the issue I'm running into is that on some servers my command works fine on others I get the error, on the last step I'm getting an operations error one of my invoke-commands. Other commands work on the same server with invoke-command such as get-service, or even the import-GPO command that I use.
The error in question:
An operations error occurred. (Exception from HRESULT: 0x80072020)
+ CategoryInfo : NotSpecified: (:) [New-GPLink], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
+ PSComputerName : 10.0.0.10
The command:
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPO" -Target $args[0]} -ArgumentList $oupath -credential $cred
I've tried every version of this command I can imagine. without [0], without argument list, just using the server ip and replacing the target with the OU path and I still get the same error, such as below.
Invoke-Command -ComputerName $serverip -scriptblock {New-GPLink -Name "GPOName" -Target ou=users,ou=site,ou=domain,dc=server,dc=com} -ArgumentList $oupath -credential $cred
The way I have it working is a .csv with the server info, it gets imported into a foreach loop and then fed into the script. I have it grab credentials and feed through. I know everything else is working because my invoke-command to import the GPO worked, all servers I ran to successfully imported the GPO. I also know my OU paths are correct because I use them locally with another script to place computers where I want them. a sample line in the csv would be something like
servername, 10.0.0.10, domain.com, OU=user,OU=site,DC=domain,DC=com
I've also ran the command locally and get a similar error:
PS> New-GPLink -Name "GPO" -Target "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink : A referral was returned from the server.
At line:1 char:1
+ New-GPLink -Name "GPO" -Target "ou=users,ou=site,dc=domain,d ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-GPLink], DirectoryServicesCOMException
+ FullyQualifiedErrorId : System.DirectoryServices.DirectoryServicesCOMException,Microsoft.GroupPolicy.Commands.NewGPLinkCommand
Please let me know if there are additional question or if you need additional info. I'm completely stumped by this issue and I appreciate any help you can provide. Thanks in advance.
Edit: All of my servers are at least 2008 R2 and are using powershell version 3,0,1,1
PS> $psversiontable.psversion
Major Minor Build Revision
----- ----- ----- --------
3 0 -1 -1
You need to specify a the domain in which your trying to apply the GPO, as well as a Domain Controller from the domain in question with the -Domain and -Server parameters respectively:
$OU = "ou=users,ou=Site,dc=domain,dc=com"
New-GPLink -Name "GPO" -Target $OU -Server "domain.com" -Domain "domain.com"
Instead of just using the domain name though, the proper way to do this, is to actually locate a Domain Controller, like so:
$DC = Get-ADDomainController -Discover -DomainName "domain.com" |Select -ExpandProperty HostName
New-GPLink -Name "GPO" -Target $OU -Server $DC -Domain "domain.tld"
Or in an environment where Get-ADDomainController is not available, you can emulate the DCLocator (aka. the underlying high-availability design of AD DS) behavior with .NET:
$DomainFqdn = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dctx = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList "Domain",$DomainFqdn
$DomainController = $[System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($dctx)
New-GPLink -Name "GPO" -Target $OU -Server $DomainController.Name -Domain $DomainFqdn