Storing wmi query in array powershell - powershell

I am trying to make a powershell program to remove a printer. The program prompts for the printer first. I can't get the printers to display separately they all display as one option.
$installedprinters = Get-WMIObject -Class Win32_Printer | Select -
ExpandProperty Name | ft -HideTableHeaders | Out-String
Write-Host $installedprinters
ForEach($name in ($installedprinters))
{[void] $objListBox.Items.Add($printer)}

Although your code doesn't satisfy Minimal, Complete, and Verifiable example rules, next code snippet could help:
$installedprinters = Get-WMIObject -Class Win32_Printer
ForEach ( $printer in $installedprinters ) {
Write-Host ( $printer.Name )
[void] $objListBox.Items.Add( $printer.Name )
}

Related

Get-Printer and set the output as a variable that I can use to set the default printer

I am currently getting a list of all the printers the computer is connected to. I want the printers to become a part of a list a user can then choose which printer to set as the default. It will then execute the next part of my script that I have already made that creates a document which then prints.
Get-Printer | Select PortName | Select-String -Pattern "SM"
This is what I would like the code to return
1. SM04
2. SM05
3. SM06
4. SM10
Please select a printer from the list: 3
Default printer has been set to: SM06.
Any help would be greatly appreciated in trying to get this working.
Thanks to js2010 and LotPings I was able to figure it out. I was even able to get a list of all the printers the computer was connected to!
Here is the code in case anyone else is trying to do something like this.
#Asks the user if they would like to switch the default printer
$defaultPrinter1 = Get-WmiObject -Query " SELECT * FROM Win32_Printer WHERE Default=$true" | select ShareName
$defaultPrinter1 = ("$defaultPrinter1").substring(12,4)
Write-Host "Barcodes will be printed at your default Printer. Your default printer is currently: $defaultPrinter1"
$printerConf = Read-Host "Would you like to change the default printer? Y/N"
#Switches dafault printer to the one specified by the user.
if ($printerConf -eq "Y")
{
Write-Host "Here is list of connected printers`n"
$printerList = Get-Printer | Select PortName | Select-String -Pattern "SM"
$printerList-replace ".*=" -replace "}.*"
$wshNet = New-Object -ComObject WScript.Network
$selectPrinter = Read-Host "`nWhat is the printers name?"
$wshNet.SetDefaultPrinter($selectPrinter)
$defaultPrinter2 = Get-WmiObject -Query " SELECT * FROM Win32_Printer WHERE Default=$true" | select ShareName
$defaultPrinter2 = ("$defaultPrinter2").substring(12,4)
Write-Host "Default Printer has been changed. It is now: $defaultPrinter2"
}
I'd use Get-WmiObject to get/set the default printer and
Out-Gridview with the -PassThru parameter to select the new default in a GUI
## Q:\Test\2019\05\20\SO_56222678.ps1
# Save old default Printer
$OldDefaultPrinter = (Get-WmiObject -Namespace root\cimv2 -Query `
"select * from Win32_Printer Where Default = TRUE" -Impersonation 3).Name
# Get-Printer matching pattern and select in Out-Gridview
$NewDefaultPrinter = (Get-Printer | Where-Object Portname -like 'SM*'|
Select-Object Name,Portname |
Out-Gridview -Title 'Select a printer' -PassThru).Name
# Set selected as new default
$Null = (Get-WmiObject -Class Win32_Printer -Filter "Name='$NewDefaultPrinter'").SetDefaultPrinter()
# Check new default is set
(Get-WmiObject -Namespace root\cimv2 -Query `
"select * from Win32_Printer Where Default = TRUE" -Impersonation 3).Name
I don't think there's a powershell command that can set the default printer. Here's how I do it in wmi. Surprisingly, get-printer doesn't return an object with a SetDefaultPrinter() method.
$printerObject = Get-WmiObject win32_printer | where name -eq $defaultprinter
$resultobj = $printerObject.SetDefaultPrinter()
"default $defaultprinter $($resultobj.returnvalue)" # showing results

CIM vs WMI returning different information

I was trying to take advantage of CIM's built-in parallel processing to get information about all the installed printers against a given subnet of computers. The script works faster than my WMI variation, but doesn't return the same information and doesn't always return as much as the Get-WmiObject call.
EDIT: The information the script drops is information about entire computers.
Here's the CIM version:
$Computer = Get-Content -Path c:\Scripts\input.txt
$Objects = foreach ($ComputerName in $Computer) {
# New CIM Instance
Write-Host Collecting information on $ComputerName
$Cim = New-CimSession -ComputerName $ComputerName
# Collect Printer Info
Get-CimInstance -CimSession $Cim -Class Win32_printer -Property deviceid, drivername, portname, systemName
# Define Hashtable properties
$ObjectProperties = #{
SystemName = $Cim.systemName
DeviceID = $Cim.deviceid
DriverName = $Cim.drivername
PortName = $Cim.portname
}
# Create new object
New-Object PSObject -Property $ObjectProperties
}
# Export Results
$Objects | Select DeviceID, DriverName, PortName, SystemName |
Export-Csv - NoTypeInformation -Path c:\Scripts\output.csv
Here's the WMI version:
$results = #()
$Computer = Get-Content -Path c:\Scripts\input.txt
# Check each computer in the list
foreach ($ComputerName in $Computer) {
$results += Get-WmiObject -Class Win32_printer -cn $ComputerName |
Select deviceid, drivername, portname, systemName
Start-Sleep -Milliseconds 500
}
# Export to CSV file
$Results | Select DeviceID, DriverName, PortName, SystemName |
Export-Csv -NoTypeInformation -Path c:\Scripts\output.csv
We sometimes need to run this script against multiple subnets. I moved to the CIM sessions because it reduced the total run of the script to consistently under 5 minutes, but if it's not going to return all of the information, it might be better to wait.
Does anyone have any idea on how to prevent CIM from dropping information?
It should be noted that WinRM is not enabled by default on these machines and the script has to force enable CIMs with the following command.
& "c:\Scripts\SnIPT\psexec.exe" \\$ComputerName -s -d -n 5 winrm.cmd quickconfig -q -force
The same WMI-class should return the same data (however CIM-cmdlets convert dates ++). Since you haven't explained what's different I'd guess it's missing output for certain computers. Usually this is because the target computer is missing Windows Management Framework 3.0 or later (think PS 3.0+) which is required for CIM. If that's the case, it should generate an error that you can catch and use to use DCOM (same as WMI) as a fallback. Ex:
$Computer = Get-Content -Path c:\Scripts\input.txt
$DCOM = New-CimSessionOption -Protocol Dcom
$Objects = ForEach($ComputerName in $Computer)
{
#New Cim Instance with fallback to DCOM
Write-Host Collecting information on $ComputerName
$Cim = $null
try {
$Cim = New-CimSession -ComputerName $ComputerName -ErrorAction Stop
} catch [Microsoft.Management.Infrastructure.CimException] {
#CIM not available on target (requires WMF 3.0+). Using DCOM (used by WMI)
try { $Cim = New-CimSession -ComputerName $ComputerName -SessionOption $DCOM -ErrorAction Stop }
catch { Write-Host $_.Exception.Message }
}
#Collect Printer Info
Get-CimInstance -CimSession $Cim -Class Win32_printer -Property DeviceID, DriverName, PortName, SystemName
#Best practice to store the original object.
#No need to create a new one with a few properties when you do it during export anyways.
#If you really need it, add "| Select-Object -Property DeviceID, DriverName, PortName, SystemName" to the previous line
}
#Export Results
$Objects | Select-Object -Property DeviceID, DriverName, PortName, SystemName | Export-Csv - NoTypeInformation -Path c:\Scripts\output.csv

Powershell IF and not condition not working properly

I'm struggling to have two conditions met, somehow it seems only one of them works. I'm trying to do :
If a user is connected AND NOT at the lock screen, ask for permission. The commands themselves have been verified and work individually but I must be missing something. Here is what i have:
if (Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName -and -not (get-process -ComputerName $poste -name logonui)) {
"ask for permission"
}
Right now it just doesn't go in this code, it skips to the lower part where something else is happening.
What is wrong with my syntax ?
I can work around it and make it work this old fashioned way from my CMD days:
Clear-Variable -name statut_user
$statut_user -eq 0
if (Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName) {$statut_user +=1}
if (-not (get-process -ComputerName $poste -name logonui)) {$statut_user += 1}
if ($statut_user -eq 2) {
"ask for permission"
}
It works, but not as clean as a proper one liner with the two conditions. Thank you for your help!
ANSWER EDIT: Thanks to vonPryz's answer below i ended up using :
$utilisateur = Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem | Select-Object -expandproperty UserName
$ecran_verr = get-process -ComputerName $poste -name logonui
if( -not ($ecran_verr) -and ($utilisateur)) {
"ask for permission"
}
Aim for clarity, not small codebase size. Instead of making WMI calls in the if statement and piping the results, consider something more readable. Like so,
$cs = gwmi -computername $p Win32_ComputerSystem
$uname = $cs | select-object -expandproperty UserName
$logonui = get-process -ComputerName $p -name logonui
if( -not ($logonui) -and ($uname )) {
# Stuff
}
This approach makes it easy to check that the WMI objects contain sensible values, whatever those may be. Then it should be easier to write a concise conditional statement.
While breaking an expression down into multiple steps is always a good idea for debugging, as demonstrated in vonPryz's helpful answer, sometimes you do want the concision of a single expression without auxiliary variables.
What is wrong with my syntax?
You're missing (...) around the Get-WmiObject ... | Select-Object ... pipeline.
To use a command or pipeline as part of a larger expression, you must always enclose it in (...) A command in PowerShell is a call to an executable - be it a cmdlet, function, alias, or external program.
A simple example:
# !! BROKEN: tokens `-eq 'jdoe'` are interpreted as *arguments for Select-Object*
# !! rather than as operator -eq and RHS 'jdoe'
if (Get-WmiObject Win32_ComputerSystem | Select-Object -Expand UserName -ne 'jdoe') {
'not jdoe'
}
# OK: (...) around the pipeline properly embeds it in the overall expression:
if ((Get-WmiObject Win32_ComputerSystem | Select-Object -Expand UserName) -ne 'jdoe') {
'not jdoe'
}
Here's a fixed version of your original command that fixes that improves other aspects too:
if (
(Get-WmiObject –ComputerName $poste –Class Win32_ComputerSystem).UserName `
-and -not `
(Get-Process -ErrorAction SilentlyContinue -ComputerName $poste -name logonui)
) {
"ask for permission"
}
Given that your Get-WmiObject call only ever outputs 1 object, you can access the .UserName property directly, which is also more efficient than piping to Select-Object -ExpandProperty.
Get-Process outputs a non-terminating error if a process by the given name cannot be found, so -ErrorAction SilentlyContinue suppresses that.
Note the use of ` as a line-continuation character, which allows spreading the conditional across multiple lines, making it much more readable.
Note that the ` must be at the very end of the line.

Multiple commandlets

I am a complete noob with regards to programming logic and some help would be greatly appreciated. My question concerns the Get-WmiObject win32_bios and Get-WmiObject win32_computersystem commandlets in the Try Block. The both work as expected if they are in there without the other, but not together. It produces an email report with all computers in domain that looks something like this:
ServerName BIOS version Serial Number
exserver DELL - 1 Phoenix ROM BIOS PLUS Version 1.10 2.7.0 3ZTVDC1
I want to add the model number, but that property is not in win32_bios (at least that I know of). So, I was going to grab it from win32_computersystem. Like I said, them both work, just not together. It always just runs whichever commandlet is first and then goes to the next computer in the list.
Import-Module ActiveDirectory
function getthebios {
$badcomp = #()
$CompList = Get-ADComputer -Filter 'name -like "*"' | select -ExpandProperty Name
foreach ($c in $CompList) {
Try {
Get-WmiObject win32_bios -ComputerName $c |
Select-Object #{l='ServerName';e= {$_.__SERVER} },
#{l='BIOS version';e = {$_.BIOSVersion} },
#{l='Serial Number';e = {$_.SerialNumber} }
Get-WmiObject win32_computersystem -ComputerName $c |
Select-Object #{l='Model Number';e = {$_.Model} }
}
Catch {
$badcomp += $c
}
}
"the following servers could not be reached:"
$badcomp
}
Send-MailMessage -To person#example.com -From "person#example.com" -SmtpServer
server.domain.net -Subject "BIOS Version Report" -body (getthebios | Sort-Object 'BIOS version'
| convertTo-Html | out-string ) -BodyAsHtml
I remember reading about functions returning all output. However the output you are using the second time would not match the object of the previous one. I imagine it is getting discarded due to a mismatch. What you need to do is create a single object with data from both queries. If you have PowerShell 3.0 this should work in place of your Try block
$bios = Get-WmiObject win32_bios -ComputerName $c | Select-Object __SERVER, BIOSVersion, SerialNumber
$computerSystem = Get-WmiObject win32_computersystem -ComputerName $c | Select-Object Model
[psCustomObject]#{
"ServerName" = $bios.__SERVER
"BIOS version" = $bios.BIOSVersion
"Serial Number" = $bios.SerialNumber
"Model" = $computerSystem.Model
}
Capture the output of both WMI calls into variables. Output the results from both in a single object. The Select-Object statements on both lines are not required but since we only need that data it made sense. Now the outputed data will match. The following would work in 2.0 PowerShell if you have that
New-Object psobject -Property #{
"ServerName" = $bios.__SERVER
"BIOS version" = $bios.BIOSVersion
"Serial Number" = $bios.SerialNumber
"Model" = $computerSystem.Model
}
Update from comments
I noticed this as well. What I did to get past that was to cast it as string
"BIOS version" = [string]$bios.BIOSVersion

Powershell - filtering WMIObject processes by more than one name

I am trying to get a list of running processes and filter by two process names - can any one tell me how to get this working?
I've so far got it working and filtering out one process name:
$rn = Get-WMIObject Win32_Process -computer servername `
-credential mydomain\administrator -filter "Name='program1.exe'" |
select -expand path
$lst = Get-Content “C:\path\path2\List.txt”
Compare-Object $lst $rn
What I want it to do is filter two process names but nothing I've tried works. Any ideas?
Here's how to get a complete set of Process objects which match a list of process names you're interested in.
$ProcessNames = #( 'explorer.exe', 'notepad.exe' )
Get-WmiObject Win32_Process -Computer 'localhost' |
Where-Object { $ProcessNames -contains $_.Name } |
Select-Object ProcessID, Name, Path |
Format-Table -AutoSize
This example finds all processes, then filters that list by sending them to a pipeline filter that checks to see if the process name is contained in the list of interesting process names. The main benefit of using the pipeline this way is that you can easily access other attributes (such as ProcessID) of the returned processes.
ProcessID Name Path
--------- ---- ----
5832 explorer.exe C:\Windows\Explorer.EXE
4332 notepad.exe C:\Windows\system32\NOTEPAD.EXE
2732 notepad.exe C:\Windows\system32\notepad.exe
Use WQL operators like OR, AND, LIKE etc:
Get-WMIObject Win32_Process -computer servername -credential mydomain\administrator -filter "Name='program1.exe' OR Name='program2.exe'"
Create an array of the processes you're after:
$processes = #('winword.exe', 'notepad.exe', 'excel.exe') | `
% {
$rn = Get-WMIObject Win32_Process -computer servername -credential mydomain\admin -filter "Name='$_'" | select -expand path
#$lst = Get-Content “C:\path\path2\List.txt”
#Compare-Object $lst $rn
write-host $rn
}
I've commented out your compare so you can see how we are looping through the array clearly.
if I understood well try this:
$rn = Get-WMIObject Win32_Process -computer servername -credential mydomain\administrator -filter "Name='program1.exe OR Name='program2.exe'"
Compare-Object $rn[0].path $rn[1].path # if there are only one instance for process with name program1.exe and program2.exe