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
Related
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
I have a script that I think will work to get my printers to output their permissions in username format, I'm getting an error "A positional parameter cannot be found that accepts argument '.PermissionSDDL'
Below is my script
$computerfile = get-content "\\test\d$\test\Ltest\test\MW
scripts\test.txt"
ForEach ($computer in $computerfile) {
Get-WmiObject Win32_Printer -ComputerName $computer |
Select-Object Name,Systemname, Local |
Format-Table -AutoSize
}
$printers = Get-Printer Name -Full.PermissionSDDL
ForEach ($Printer in $Printers) {
$objSID = New-Object System.Security.Principal.SecurityIdentifier
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
$objUser.Value
}
This happens, as object property is referred in improper a way.
This
$printers = Get-Printer Name -Full.PermissionSDDL
attempts to pass a parameter -Full.PermissionSDDL that Get-Printer doesn't support. What's more, the -Name switch is missing the switch identifier and, well, actual printer name.
The proper syntax is
$printers = (Get-Printer -Name $myPrinter -Full).PermissionSDDL
I am learning to create new objects and combine properties from other objects. In this script I want to find out what the PS version is and also add some other properties like OS, IP etc... but I am running into 2 problems. We have 6 domains and I can't seem to iterate over each domain. I tried (Get-ADForest).Domains and can see the list of domains. It still only returns objects in the domain my workstation belongs to. The second issue is the Invoke-Command. The version always returns 5. I know many of the servers being returned do not have PSVersion 5.
function Get-PSVersion {
(Invoke-Command -Scriptblock {$PSVersionTable.PSVersion}) | Select Major
}
$servers = Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")} -Properties * |
ForEach-Object {
$ps = Get-PSVersion
$server = $_
New-Object -TypeName PSObject -Property #{
Name = $server.Name
OS = $server.OperatingSystem
IPAddress = $server.IPv4Address
Location = $server.CanonicalName
PSVersion = $ps.Major
}
}
$servers | Select Name,Location,OS,IPAddress,PSVersion | FT -AutoSize
Ok so starting with the Invoke-Command, You need to tell that cmdlet which server to target, just calling it as you loop over server names will keep calling it on your local computer, so you'll need to use the -computername parameter, and provide your function an argument to pass to invoke-command. Which would look something like this:
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion | Select Major})
}
You'll notice I also moved your select, this isn't strictly necessary but imo it looks cleaner and means slightly less data gets sent over the network. note that this will create an object with a single property called Major, if you want just the version number returned as an integer you'd want to do it like this
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion.Major})
}
You'll need to add an extra loop into the script if you want to target more than one domain, basically you want an array of the domains you wish to target and then loop over that array calling get-adcomputer for each and specifying the domain name for the -server parameter. I've put a simplified example below you can incorporate into your own code.
$arr = #("test.domain","othertest.domain")
foreach($domain in $arr){
Get-ADComputer -Filter * -Server $domain
}
Hope that helps!
Got it to work. Thanks for the assistance.
clear-host
$arr = #("x.local","x.local")
foreach($domain in $arr){
$servers = (Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")}-Server $domain -Properties *|Select -First 10)
}
$MasterList = #()
foreach ($server in $servers) {
$MyObj = New-Object PSObject -Property #{
Name = $server.Name
Os = $server.OperatingSystem
Ip = $server.IPv4Address
PSV = Invoke-Command -ComputerName $server.Name -ScriptBlock {$PSVersionTable.psversion}
}
$MasterList += $MyObj
}
$MasterList|Select Name,PSV,OS,IP
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 )
}
First time poster here, I'm a bit of a beginner and I've been keen to get my PowerShell scripting skills up to scratch and I'm come across something rather confusing...
I've made a script to query a collection of computers and I want to query Win32_OperatingSystem but only extrapolate the Build number so I can populate my PSObject with it. I'm trying to add some If logic so that if the build number is 7601, I can write a message under my OS column.
The problem I'm having is that the BuildNumber values are coming out as #{BuildNumber=7601} instead of 7601 for instance. That, and my If statement is borked.
$Machines = Get-Content .\Computers.txt
Foreach($Machine in $Machines)
{
$sweet = (Get-WmiObject -Class Win32_OperatingSystem -computer $Machine | Select-Object BuildNumber)
$dversion = if ($sweet -eq "#{BuildNumber=7601}") {Yes!} else {"Nooooo!"}
New-Object PSObject -Property #{
ComputerName = $Machine
Sweet = $sweet
OS = $dversion
}
}
The issue is that the Get-WMIObject cmdlet is returning a Hash Table. Then the Select-Object is returning just the BuildNumber section you want, the BuildNumber property and it's value. You need to add the -ExpandProperty parameter to only get the value back, not the name/value pair.
Get-WMIObject -Class Win32_OperatingSystem | Select-Object BuildNumber
Returns
#{BuildNumber=7601}
With ExpandProperty
Get-WMIObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber
Returns
7601
Just another option with a ping test to skip unavailable machines.
Get-Content .\Computers.txt | Where-Object {Test-Connection -ComputerName $_ -Count 1 -Quiet} | Foreach-Object {
$sweet = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $_ | Select-Object -ExpandProperty BuildNumber
New-Object PSObject -Property #{
ComputerName = $_.__SERVER
Sweet = $sweet
OS = if ($sweet -eq 7601) {'Yes!'} else {'Nooooo!'}
}
}