I have a csv as below :
Server OS HotFixID
Comp1 Win12 KB452
Comp1 Win12 KB453
svrname3 Win8 KB134
I have written below script for checking OS and if its matched then it should check if the server is having the same HotfixID or not.
$file = Import-Csv .\Desktop\hotfix.csv
if($Win12 = $file | where {$_.os -eq "Win12"} | select Source, HotFixID)
{
Get-HotFix -ComputerName ($Win12.Source) -Id ($Win12.HotFixID)
}
else
{
$Win8 = $file | where {$_.os -eq "Win8"} | select Source, HotFixID
Get-HotFix -ComputerName ($Win8.Source) -Id ($Win8.HotFixID)
}
problem is with output.. I have 2 Win12 server in csv, but I am getting 4 output 2 as duplicate.. I am able to understand that here one nested loop is running but unable to rectify it. Please!! let me know how can we fix this issue.
Assumed I understood your task correct you should iterate over all elements in your CSV file and check each item indivually.
$CSVList = Import-Csv .\Desktop\hotfix.csv
foreach ($CSVItem in $CSVList) {
if ($CSVItem.os -eq 'Win12' -or $CSVItem.os -eq 'Win8') {
if (Test-Connection -ComputerName $CSVItem.Source) {
Get-HotFix -ComputerName $CSVItem.Source -Id $CSVItem.HotFixID
}
}
}
You have multiple entries with Win12 so $Win12 is an array of PSCustomObject. When you use ($Win12.Source) it will output an array of all sources. (Comp1,Comp1). Get-Hotfix accepts this array and tests $Win12.HotFixID (which is an array, too) with each of the sources. Thats the reason you get each Item the Number of times they apear in the CSV. To avoid this, process each line in the CSV on their own. If you want to keep your structure, your code would look like this:
if($Win12 = $file | where {$_.os -eq "Win12"} | select Source, HotFixID)
{
$Win12 | foreach {
Get-HotFix -ComputerName ($_.Source) -Id ($_.HotFixID)
}
}
else
{
$Win8 = $file | where {$_.os -eq "Win8"} | select Source, HotFixID
$Win8 | foreach {
Get-HotFix -ComputerName ($_.Source) -Id ($_.HotFixID)
}
}
However the Code of Olaf is cleaner and does the same.
Related
I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.
I found this script Get-ProcessPlus by PMental here on Stack Overflow. With the help of zett42
and several others I manged to get it to run (very new to PS). Thanks guys, It had everything I was really looking for.
I opted too see if I could have the script add one more feature. I wanted it too return the commandline value of the process. I got it to partially work. With my modifications it still runs as default, and by Id, but no longer by name. I have done quite a bit of reading but still can not get it to work properly. Here is the code and my mods. Any help would be appreciated.
*$Command = Get-WmiObject Win32_Process | select name, CommandLine*
function Get-ProcessPlus {
[CmdletBinding(DefaultParameterSetName = 'Default')]
param (
[Parameter(ParameterSetName='ProcessName',Position = 0)]
[string[]]
$Name,
*[Parameter(ParameterSetName='CommandLine',Position = 0)]
[string[]]
$Command,*
[Parameter(ParameterSetName='PID',Position = 0)]
[int[]]
$Id
)
# Check which parameter set is in use and get our processes
switch ($PSCmdlet.ParameterSetName) {
'ProcessName' {
$AllProcesses = Get-Process -Name $Name
break
}
*'CommandLine' {
$AllProcesses = Get-Process -Name $Command
break
}*
'PID' {
$AllProcesses = Get-Process -Id $Id
break
}
default { $AllProcesses = Get-Process }
}
foreach ($Process in $AllProcesses) {
# Retrieve TCP and UDP Connection information for the current process (if any)
$UDPConnections = Get-NetUDPEndpoint -OwningProcess $Process.Id -ErrorAction Ignore |
Select-Object LocalAddress,LocalPort
$TCPConnections = Get-NetTCPConnection -OwningProcess $Process.Id -State Listen -ErrorAction Ignore |
Select-Object LocalAddress,LocalPort
$TCPPorts = $TCPConnections.LocalPort | Where-Object { $null -ne $_} | Select-Object -Unique
$UDPPorts = $UDPConnections.LocalPort | Where-Object { $null -ne $_} | Select-Object -Unique
$TCPAddresses = $TCPConnections.LocalAddress | Select-Object -Unique
$UDPAddresses = $UDPConnections.LocalAddress | Select-Object -Unique
# Collect and output all information about the current process
[PSCustomObject] #{
'ProcessName' = $Process.ProcessName
'Id' = $Process.Id
'Description' = $Process.Description
'Path' = $Process.Path
*'CommandLine' = $Process.Command*
'CPU usage (s)' = $Process.CPU
'TCP Addresses' = $TCPAddresses
'TCP Ports' = $TCPPorts
'UDP Addresses' = $UDPAddresses
'UDP Ports' = $UDPPorts
}
}
}
You need to match the Get-WMIObject output to the Get-Process Output. In your case, just replace this line:
'CommandLine' = $Process.Command
With this:
# Query WMI for process command line
'CommandLine' = Get-WmiObject -Query "
SELECT CommandLine from Win32_Process WHERE ProcessID = $($Process.ID)" |
# Select only the commandline property so we can display it
Select -ExpandProperty CommandLine
My output looks like so:
Get-ProcessPlus -Name notepad
ProcessName : notepad
Id : 10568
Description : Notepad
Path : C:\WINDOWS\system32\notepad.exe
CommandLine : "C:\WINDOWS\system32\notepad.exe" C:\temp\test.txt
CPU usage (s) : 0.390625
I had a quick questions regarding exporting to CSV.
How is it decided on what to export to a CSV? What do i mean by that? The below example is just reading off a list and querying the Systemversion off AD. Then, i am associating it with the proper version we go off by. Unfortunately, it does export it properly or sometimes at all. Im not sure if this is making any sense so pardon the ignorance on my part.
$CStrings = Get-Content "C:\users\ME\Desktop\Comps.txt"
$comps = Foreach($Computer in $CStrings){
$VS = Get-ADComputer -Identity:"CN=$Computer,OU=MY Computers,OU=LAB " -Properties:operatingSystemVersion | Select-Object -ExpandProperty operatingsystemversion
if($VS -like "21543"){
"$Computer", "Version 45.32"}
else {
if($VS -like "21544"){
"$Computer", "Version 45.33"}
else{
if($VS -like "21545"){
"$Computer", "Version 45.34"}
else{
"$Computer", "$VS"
}
}
}
}
Foreach($comp in $comps){
Write-Output $comp | Out-File C:\users\ME\Desktop\comps.csv
}
The result in the CSV comes out to just one version output, but nothing else. I tried "$computer - Versions 45.32" which joins it, and exports each computer but, its all in one column.
Before i get hounded at, i did check other forums including ms_docs lol would rather have someone dumb it down for me if possible.
Summary: Looking to keep the Computer Name, and Version in a separate Column.
Happy New Years BTW!(:
One of the easiest ways is to create a list of custom objects and then simply export that list to csv. Here is one way to do that with your current requirements.
$CStrings = Get-Content "C:\users\ME\Desktop\Comps.txt"
$comps = Foreach($Computer in $CStrings)
{
$VS = Get-ADComputer -Identity:"CN=$Computer,OU=MY Computers,OU=LAB " -Properties operatingSystemVersion | Select-Object -ExpandProperty operatingsystemversion
$VS = switch -regex ($VS)
{
'21543' {"Version 45.32"}
'21544' {"Version 45.33"}
'21545' {"Version 45.34"}
default {$_}
}
[PSCustomObject]#{
ComputerName = $Computer
OSVersion = $VS
}
}
$comps | Export-Csv -Path C:\users\ME\Desktop\comps.csv -NoTypeInformation
I need to include 'Installed' and 'Not Installed' data in one CSV
I think I need to incorporate an -OR logical operator to include TRUE/FALSE
output in one CSV. Idk how to do that yet.
There's a folder with many *ServerData files that contain a list of KBs with
possible duplicate KBs.
There is a *ServerData file for each server, with possible duplicate files.
I want to test whether any of them contain KB2151757 and KB4556403.
Then output the results to a csv with a status of either Installed or Not
Installed.
Currently it only returns a list of computers with the KB installed.
If the $patch is not found, it currently returns nothing (null).
For each $computer searched, it needs to return the specified fields for the
[PSCustomObject]
I'm thinking that maybe I just need to take a function to find 'installed' and a function to find 'not installed' and add the results together to export. Idk how to do that. I feel like there must be an easier way.
Click to view a sample of the CSV
$computers = Get-Item -path F:\*ServerData | Select-Object -ExpandProperty basename
$patch = gc -path F:\*ServerData | Sort-Object -Unique | Select-String KB2151757, KB4556403 #'KB(\d+)'
$output = ForEach ($computer in $computers) {
ForEach ($kb in $patch) {
if ($null -eq $patch){
[PSCustomObject]#{
Status = 'Not Installed'
Server = $computer
KB = $kb
}
} else{
[PSCustomObject]#{
Status = 'Installed'
Server = $computer
KB = $kb
}
}
}
}
$output | Export-csv C:\KB-Report.txt -notypeinformation -delimiter ',' -encoding utf8
If you start by grouping the files by the associated computer name, then the procedure becomes straightforward (pseudocode):
for each Computer
for each ExpectedPatch
if ServerData for Computer contains ExpectedPatch
Output object with 'Installed' status for ExpectedPatch on Computer
else
Output object with 'NotInstalled' status for ExpectedPatch on Computer
So let's give that a try:
# Define the articles we're looking for
$ExpectedPatches = 'KB2151757', 'KB4556403'
# Enumerate and group data files by computer name, output as hashtable
# The resulting hashtable will have the computer name is Name and the associated files as its value
$ServerDataPerComputer = Get-Item -Path F:\*ServerData |Group BaseName -AsHashtable
foreach($Computer in $ServerDataPerComputer.GetEnumerator())
{
foreach($Patch in $ExpectedPatches)
{
# Pipe all the file references to Select-String, look for the KB ID, return after the first match if any
$Status = if($Computer.Value |Select-String "\b$Patch\b" |Select-Object -First 1){
'Installed'
}
else {
# Select-String didn't find the KB ID in any of the files
'NotInstalled'
}
[pscustomobject]#{
Status = $Status
Server = $Computer.Name
KB = $Patch
}
}
}
I am trying to get a list of servers and the last time they rebooted to show in a table. However, if it doesn't respond to a ping, I just need it to show in the list. I can't seem to figure out how to get it to add to the table after else.
Import-CSV $Downtime | % {
if(Test-Connection $_.server -Quiet -count 1){
Get-WmiObject Win32_OperatingSystem -ComputerName $_.server |
select #{LABEL="Name"; EXPRESSION = {$_.PSComputerName}}, #{LABEL="Last Bootup"; EXPRESSION = {$_.convertToDateTime($_.LastBootupTime)}}
}
else{#{LABEL="Name"; EXPRESSION = {$_.server}}
}
} | Out-GridView
I can always save the else results in a text file but this would be more convenient.
You need to make the same object, with the same properties!, in both cases so that PowerShell will understand the association between the two. The follwing example builds a custom hashtable using the if/else and outputs the object for each loop pass.
Import-CSV $Downtime | ForEach-Object {
$props = #{}
$server = $_.server
if(Test-Connection $server -Quiet -count 1){
$wmi= Get-WmiObject Win32_OperatingSystem -ComputerName $server
$props.Name = $wmi.PSComputerName
$props."Last Bootup" = $wmi.convertToDateTime($wmi.LastBootupTime)
}else{
$props.Name = $server
$props."Last Bootup" = "Could not contact"
}
New-Object -TypeName psobject -Property $props
} | Out-GridView
I used $server as the $_ changes context a couple of time so we wanted to be able to refer to the current row in the CSV we are processing.
I don't know what your PowerShell version is so I will assume 2.0 and create objects that support that.
In both cases an object is created with a Name and Last Bootup property which is populated based on the success of the ping.
As an aside I had a similar question a while ago about created similar object based output.