Just wanted to post my solution here in case anyone needs it. We recently had to replace a printer that was widely used around the organization. We are using a universal driver for this printer and just needed to update the name of the printer on the machines. Here is the powershell script I came up with.
#Pulls printer name, but it is formatted and not clean. Replace IP with printer IP
$PrinterExtract = wmic printer where "PortName LIKE 'IP%'" GET Name
#removes 'name' string from variable
$PrinterExtract = $PrinterExtract -replace 'Name',''
#removes all empty lines and formatting and creates a new variable
$PrinterName = ($PrinterExtract | Where { $_ -ne "" } | Out-String).Trim()
#pulls printer object
$PrinterObject = Get-Printer -Name $PrinterName
#renames printer. Replace new printer name with the name of new printer
Rename-Printer -InputObject $PrinterObject -NewName "New Printer Name"
I was trying to perform this task without using wmic but could not figure out a way to pull an existing printer's name from an IP. If anyone can provide a more modern solution, it would be appreciated.
Figured it out, if anyone needs it
#Pulls printer name, but it is formatted and not clean
$PrinterExtract = (Get-Printer | Where-Object {$_.PortName -Like "IP"} | Select Name | Where {$_ -ne ""} | Out-String).Trim()
#removes 'name' string from variable
$PrinterExtract = $PrinterExtract -replace 'Name',''
#removes '----' string from variable
$PrinterExtract = $PrinterExtract -replace '----',''
#removes formatting and empty lines
$PrinterName = ($PrinterExtract | Where { $_ -ne "" } | Out-String).Trim()
#pulls printer object into new variable
$PrinterObject = Get-Printer -Name $PrinterName
#renames printer
Rename-Printer -InputObject $PrinterObject -NewName "Printer Name"
Related
1
This is part of the code I created:
$NameOrigin = "BP |"
$NameBackup = "Backup"
Get-CimInstance win32_logicaldisk |
ForEach-Object{
if ($_.VolumeName -match "^$NameOrigin(.+)" -and $_.VolumeName -notmatch "^$NameBackup(.+)" ){
$Employee = $Matches[1]
$FSRootOrigin = "{0}{1}" -f $_.DeviceId,'\'
}
}
Basically it checks if there is any drive with the word "BP |" and that does not contain the word "Backup" in the volume label and gets the letter of that drive.
Given that, if you look at the $Employee variable, the idea is that it extracts what is after "|" so I can use it in another step of the script, however for some reason it doesn't work when the drive is named using "|".
When the $NameOrigin variable was "BP -" I was able to extract the information that was after the "-".
Why doesn't it work using "|"?
How can I get around this?
I guess the answer is to escape the pipe because it's regex for "or".
$NameOrigin = "BP \|"
I am blocked with my code
I will try to explain what I would like to do with it,
my code is to scan the different windows services, to keep in memory only what uses an .exe and then to search among them the ones that the users have full control of.
I would like it to display the service and its rights at the end.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName | Out-Null
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0]
foreach ($right in $var ){
if ( (Get-Acl $var).Access -ccontains "BUILTIN\Utilisateurs FullControl "{
Write-Warning " Exploit detected "
}
}
}
thank you in advance for your feedback
Get-CimInstance Win32_Service |
Where-Object { $_.PathName -like '*.exe*'} |
Select-Object Name, State, Pathname, StartName |
ForEach-Object {
$_.PathName = ($_.PathName -split '(?<=\.exe\b)')[0].Trim('"')
Add-Member -PassThru -InputObject $_ Acl (Get-Acl -LiteralPath $_.PathName)
} |
Where-Object {
$_.Acl.Access.Where({
$_.IdentityReference -ceq 'BUILTIN\Utilisateurs' -and
$_.FileSystemRights -eq 'FullControl'
}, 'First').Count -gt 0
}
Note that I've replaced Get-WmiObject with Get-CimInstance, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (v6+), where all future effort will go, doesn't even have them anymore. Note that WMI still underlies the CIM cmdlets, however. For more information, see this answer.
The above uses the ForEach-Object call to:
update the .PathName property of each object to contain only the - unquoted - path of the executable with each service.
add an .Acl property to each object via Add-Member, containing the service executable's ACL, obtained via Get-Acl.
The resulting list of objects is then filtered by those whose service-executable ACL contains an entry for identity BUILTIN\Utilisateurs[1] with full control over the executable.
That is, the resulting objects are effectively those for which you meant to issue Write-Warning " Exploit detected "
As for what you tried:
$services = ... | Out-Null by definition captures nothing[2] in variable $services, given that Out-Null's purpose is to suppress output.
While $var = "{0}.exe" -f ($Service.PathName -Split ".exe")[0] does extract the executable path (although .exe should be \.exe\b, for robustness), it may include enclosing double quotes, which should be stripped.
It's unclear where $rights comes from.
You cannot use -ccontains to match across multiple properties of an object, and note that the purpose of the -contains operator and its variants is to test presence of a value in full, in a collection, not to look for a substring in a single string.
[1] It's interesting to see that these identity references are localized; the equivalent on a US-English system would be BUILTIN\Users. Generally, it would be better to obtain a culture-independent representation of this identity, namely its SID, and use that for comparison: $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq 'S-1-5-32-545'
[2] Loosely speaking, $null; technically, it is the singleton value that PowerShell uses to signal "no output received", [System.Management.Automation.Internal.AutomationNull]::Value.
If I begin by the begining you terminate your first line by | out-null so $services contains nothing.
Then you forgot a ")" in your if.
You should present your code with indentations.
Be carefull :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split ".exe")[0]
Gives :
C:\WINDOWS\system32\ms.exe
So use a '\' before the '.' :
"{0}.exe" -f ("C:\WINDOWS\system32\msiexec.exe /V"-Split "\.exe")[0]
which gives (regular expression story ?):
C:\WINDOWS\system32\msiexec.exe
So it will give something like that.
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, State, Pathname, StartName
foreach ($service in $services) {
$var = "{0}.exe" -f ($Service.PathName -Split "\.exe")[0]
if ((Get-Acl $var.Trim('"') -ErrorAction Stop) -ccontains "BUILTIN\Utilisateurs FullControl "){
Write-Warning " Exploit detected "
}
}
I am trying to declare and set a variable in a powershell command. Is that possible?
I was hoping to do something like this:
"$name" = "I219" | Get-NetAdapter | where Name -Match "$name"
Is this possible or can this only be done in a .ps script?
It can be done easily by just hitting enter in the console after declaring your variable:
$name = "I219" # now hit enter
To access the variable, type it in the console and hit enter again:
$name # hit enter => returns I219
Now use it with your command:
Get-NetAdapter | where { $_.Name -Match $name }
Or as a one-liner:
$name = "I219"; Get-NetAdapter | where { $_.Name -Match $name }
I am writing a super-easy script in PowerShell. The target of this script is to read a list of server names from a txt file and a command block from another txt file. The result of the operation shold be a third txt file containing the information.
Here some code:
cls
$usr = Read-Host "Please insert username, you'll be asked for password later"
$path = Read-Host "Insert a valid path for ServerList.txt file"
$serverList = Get-Content -Path $path | Out-String
$path = Read-Host "Insert a valid path fom Command.txt file"
$commandBlock = Get-Content -Path $path | Out-String
echo "Command: " $commandBlock "will be executed on " $serverList
echo "Press CTRL+Z to abort or"
pause
Invoke-Command -ComputerName $serverList -ScriptBlock { $commandBlock } -credential $usr
Serverlist.txt is a plain text containing something like "server1,server2,server3" and command.txt contain only this "Get-WmiObject Win32_BIOS | Select-Object SerialNumber"
Why the error is Invoke-Command : One or more computer names are not valid. If you are trying to pass a URI, use the -ConnectionUri parameter, or pass URI objects
instead of strings. ?
I even tried to substitute $serverlist with $serverlist.toString() but it's not working. I read somewhere that in this case $serverlist is an Array, how do I do to make everything work?
Consider that https://technet.microsoft.com/en-us/library/hh849719.aspx Invoke-Commands work with "server1,server2,server3" format if you put the string via console.
Your $serverList isn't a list, it's a single string of server1,server2 etc. To make it into an array, you can use -split to split the string by commas.
$serverList = Get-Content -Path $path | Out-String
$serverList = $serverList -split ","
For further understanding of why this doesn't work as you expect, please see the parsing and command syntax help files:
Get-Help about_Parsing
Get-Help about_Command_Syntax
$serverlist
When your text file contains the line server1,server2,server3, this command:
Get-Content -Path .\file.txt | Out-String
Just results in the string server1,server2,server3 and a newline - that's not a valid hostname.
Either format your text file like this (Get-Content automatically splits on line breaks):
server1
server2
server3
or split the string(s) from the file yourself:
$Serverlist = Get-Content -Path $Path | ForEach-Object { $_ -split "," }
$commandblock
For the command block part to work, you can't just drop a string into a ScriptBlock and expect it to execute - you need to recreate it as executable code:
$Code = Get-Content -Path $path -Raw
$CommandBlock = [scriptblock]::Create($Code)
# Now you can do this
Invoke-Command -ScriptBlock $CommandBlock
I have a task to update all client printer settings during a migration from and old 2003 R2 print server to a new 2008 R2 print server. All clients are Win7 with Powershell 2.0 and I created a script that adds new printers and deletes old printers on the client.
However, it mess up the default printer setting on the client, it seems to be random if it changes the default printer to a randrom printer or if no default printer is set at all.
I was thinking to use the method Get-WmiObject -Class Win32_Printer -Filter "Default = $true" and that works, I can see the correct (and old) default printer.
But if I try to set the new default printer to the same name, it fails (or more precisely, it just gets random what happens).
Maybe I am putting the function $printer.SetDefaultPrinter() on the wrong place?
Code:
Param (
$newPrintServer = "Server2",
$PrinterLog = "\\LogSVR\PrintMigration$\PrintMigration.csv"
)
<#
#Header for CSV log file:
"COMPUTERNAME,USERNAME,PRINTERNAME,RETURNCODE-ERRORMESSAGE,DATETIME,STATUS" |
Out-File -FilePath $PrinterLog -Encoding ASCII
#>
Try {
Write-Verbose ("{0}: Checking for printers mapped to old print server" -f $Env:USERNAME)
$printers = #(Get-WmiObject -Class Win32_Printer -Filter "SystemName='\\\\Server1'" -ErrorAction Stop)
$DefPrinter = Get-WmiObject -Class Win32_Printer -Filter "Default = $true"
If ($printers.count -gt 0) {
ForEach ($printer in $printers) {
Write-Verbose ("{0}: Replacing with new print server name: {1}" -f $Printer.Name,$newPrintServer)
$newPrinter = $printer.Name -replace "Server1",$newPrintServer
$returnValue = ([wmiclass]"Win32_Printer").AddPrinterConnection($newPrinter).ReturnValue
If ($returnValue -eq 0) {
"{0},{1},{2},{3},{4},{5}" -f $Env:COMPUTERNAME,
$env:USERNAME,
$newPrinter,
$returnValue,
(Get-Date),
"Added Printer" | Out-File -FilePath $PrinterLog -Append -Encoding ASCII
Write-Verbose ("{0}: Removing" -f $printer.name)
$printer.Delete()
"{0},{1},{2},{3},{4},{5}" -f $Env:COMPUTERNAME,
$env:USERNAME,
$printer.Name,
$returnValue,
(Get-Date),
"Removed Printer" | Out-File -FilePath $PrinterLog -Append -Encoding ASCII
$DefPrinter.SetDefaultPrinter()
} Else {
Write-Verbose ("{0} returned error code: {1}" -f $newPrinter,$returnValue) -Verbose
"{0},{1},{2},{3},{4},{5}" -f $Env:COMPUTERNAME,
$env:USERNAME,
$newPrinter,
$returnValue,
(Get-Date),
"Error Adding Printer" | Out-File -FilePath $PrinterLog -Append -Encoding ASCII
}
}
}
} Catch {
"{0},{1},{2},{3},{4},{5}" -f $Env:COMPUTERNAME,
$env:USERNAME,
"WMIERROR",
$_.Exception.Message,
(Get-Date),
"Error Querying Printers" | Out-File -FilePath $PrinterLog -Append -Encoding ASCII
}
I may be misunderstanding, but the default printer(defprinter) is also located on server1, right? so you create a link defprinter to printer x. then you delete all printers(including printer x) and you try to make defprinter(which no longer exists) default printer again. That won't work and a random printer will get the default attribute.
1st, you should store the unique printername($printer.name) of the defprinter before the loop starts. then when the loop is done: you search for the newly created printer wmi-object that represents the previous default printer(using the printername you saved pre-loop) and make that default..