Use Powershell to insert data into multiple columns in SQL Server - sql-server

I'm trying to insert data collected from VMware vCenter into a database using Powershell and SQL Server.
This is what I have for now:
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=.\sqlexpress;Initial Catalog=Computer;Integrated Security=SSPI;"
$conn.open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.connection = $conn
$val = 0
$clusterinf = foreach($cluster in Get-Cluster){
$esx = $cluster | Get-VMHost
$ds = Get-Datastore -VMHost $esx | where {$_.Type -eq "VMFS"}
$cluster | Select #{N="VCname";E={$cluster.Uid.Split(':#')[1]}},
#{N="DCname";E={(Get-Datacenter -Cluster $cluster).Name}},
#{N="Clustername";E={$cluster.Name}},
#{N="TotalPhysicalMemory";E={[Math]::Round(($esx | Measure-Object -Property MemoryTotalMB -Sum).Sum /1KB)}},
#{N="TotalUsedMemory";E={[Math]::Round(($esx | Measure-Object -Property MemoryUsageMB -Sum).Sum /1KB)}},
#{N="AvailableMemroy";E={[Math]::Round(($esx | Measure-Object -InputObject {$_.MemoryTotalMB - $_.MemoryUsageMB} -Sum).Sum /1KB)}},
#{N="TotalCPU";E={[Math]::Round(($esx | Measure-Object -Property CpuTotalMhz -Sum).Sum /1KB)}},
#{N="TotalUsedCPU";E={[Math]::Round(($esx | Measure-Object -Property CpuUsageMhz -Sum).Sum /1KB)}},
#{N="AvailableCPU";E={[Math]::Round(($esx | Measure-Object -InputObject {$_.CpuTotalMhz - $_.CpuUsageMhz} -Sum).Sum /1KB)}},
#{N="TotalDiskSpace";E={[Math]::Round(($ds | where {$_.Type -eq "VMFS"} | Measure-Object -Property CapacityMB -Sum).Sum /1KB)}},
#{N="ConfiguredDiskSpace";E={[Math]::Round(($ds | Measure-Object -InputObject {$_.CapacityMB - $_.FreeSpaceMB} -Sum).Sum /1KB)}},
#{N="AvailableDiskSpace";E={[Math]::Round(($ds | Measure-Object -Property FreeSpaceMB -Sum).Sum /1KB)}},
#{N="Total Configured Memory GB For Powered on VMs";E={[Math]::Round(($_ | Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"} | %{$_.MemoryMB} | Measure-Object -Sum | Select -ExpandProperty Sum)/1KB) }}
$cmd.commandtext = "INSERT INTO Clusterinfo (VCname) VALUES('{$val}')" -f $clusterinf.VCname
$cmd.ExecuteNonQuery()
$val++
}
$conn.close()
And this is working as intended i believe. Now my question is, how do I continue with the next column? So that I can add DCname, Clustername, TotalPhysicalMemory and so on?
I have been trying with this insert statement:
$cmd.commandtext = "INSERT INTO Clusterinfo (VCname, DCname, CLustername) VALUES('$($clusterinf.VCname)','$($clusterinf.DCname)','$($clusterinf.Clustername)')"
But its not realy giving the wanted result, as it will insert every VCname, DCname and so on, into one row, and not into different rows.
I have been looking at this question: Powershell How to query multiple classes and write in to SQL Table
But I can't seem to get my head around a solution to my own question.
Anybody got an idea on how to solve this?

$clusterinf needs to be the object with the data of the current cluster, not the result of the entire foreach-loop.
I would try something like this:
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=.\sqlexpress;Initial Catalog=Computer;Integrated Security=SSPI;"
$conn.open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.connection = $conn
foreach($cluster in (Get-Cluster)){
$esx = $cluster | Get-VMHost
$ds = Get-Datastore -VMHost $esx | where {$_.Type -eq "VMFS"}
$clusterinf = $cluster | Select #{N="VCname";E={$cluster.Uid.Split(':#')[1]}},
#{N="DCname";E={(Get-Datacenter -Cluster $cluster).Name}},
#{N="Clustername";E={$cluster.Name}},
#{N="TotalPhysicalMemory";E={[Math]::Round(($esx | Measure-Object -Property MemoryTotalMB -Sum).Sum /1KB)}},
#{N="TotalUsedMemory";E={[Math]::Round(($esx | Measure-Object -Property MemoryUsageMB -Sum).Sum /1KB)}},
#{N="AvailableMemroy";E={[Math]::Round(($esx | Measure-Object -InputObject {$_.MemoryTotalMB - $_.MemoryUsageMB} -Sum).Sum /1KB)}},
#{N="TotalCPU";E={[Math]::Round(($esx | Measure-Object -Property CpuTotalMhz -Sum).Sum /1KB)}},
#{N="TotalUsedCPU";E={[Math]::Round(($esx | Measure-Object -Property CpuUsageMhz -Sum).Sum /1KB)}},
#{N="AvailableCPU";E={[Math]::Round(($esx | Measure-Object -InputObject {$_.CpuTotalMhz - $_.CpuUsageMhz} -Sum).Sum /1KB)}},
#{N="TotalDiskSpace";E={[Math]::Round(($ds | where {$_.Type -eq "VMFS"} | Measure-Object -Property CapacityMB -Sum).Sum /1KB)}},
#{N="ConfiguredDiskSpace";E={[Math]::Round(($ds | Measure-Object -InputObject {$_.CapacityMB - $_.FreeSpaceMB} -Sum).Sum /1KB)}},
#{N="AvailableDiskSpace";E={[Math]::Round(($ds | Measure-Object -Property FreeSpaceMB -Sum).Sum /1KB)}},
#{N="Total Configured Memory GB For Powered on VMs";E={[Math]::Round(($_ | Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"} | %{$_.MemoryMB} | Measure-Object -Sum | Select -ExpandProperty Sum)/1KB) } }
$cmd.commandtext = "INSERT INTO Clusterinfo (VCname, Clustername, TotalPhysicalMemory, TotalUsedMemory, AvailableMemroy, TotalCPU, TotalUsedCPU, AvailableCPU, TotalDiskSpace, ConfiguredDiskSpace, AvailableDiskSpace) VALUES('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}')" -f $clusterinf.VCname, $clusterinf.Clustername, $clusterinf.TotalPhysicalMemory, $clusterinf.TotalUsedMemory, $clusterinf.AvailableMemroy, $clusterinf.TotalCPU, $clusterinf.TotalUsedCPU, $clusterinf.AvailableCPU, $clusterinf.TotalDiskSpace, $clusterinf.ConfiguredDiskSpace, $clusterinf.AvailableDiskSpace
$cmd.ExecuteNonQuery()
}
$conn.close()
As a side note, you should replace commandtext with values with parameters. It's less prone to SQL injection and is alot cleaner in my opinon. See this link

Related

Object of arrays in powershell not showing anything in CSV

I have an object which contains multiple arrays of strings. Nothing I do can display these in a decent format in Powershell. I have looked everywhere on this site and many others and been unable to find an answer. Is this something that should be done with Python or a separate programming language? It appears powershell doesn't want users to export arrays to CSV. Anyways let me know if you all have a fix for this.
Import-Module VMware.VimAutomation.Core
Connect-VIServer -Server vcenter-appliance.swacu.net -Force $prodservers = get-vm | select Name Connect-VIServer -Server drvcenter-appliance.swacu.net -Force $replicaserversfull = get-vm | select Name
$notinprod = Foreach($replica in $replicaservers){
$shortname = $replica.name.trim('_replica')
$shortnametwo = if($shortname -like "*_TTN"){$shortname.trim('_TTN')}
$shortnames = New-Object -TypeName PSObject -Property #{
ShortName = $replica.name.trim('_replica')
}
if($prodservers.name -contains $shortname){
}
elseif($prodservers.name -contains $shortnametwo) {
}
else{
New-Object -TypeName PSObject -Property #{
SystemName = $replica.name
} | Select SystemName
} }
$NotInReplica = Foreach($prodserver in $prodservers){
$half = $replicaservers.name.trim('_replica')
$correct = if($half -like "*_TTN"){$half.trim('_TTN')}
if($half -contains $prodserver.name){
}
elseif($correct -contains $prodserver.name) {
}
else{
New-Object -TypeName PSObject -Property #{
SystemName = $prodserver.name
} | Select SystemName
}
}
Write-host $half $whole = New-Object -TypeName PSObject -Property #{
Replica = $replicaservers.name | out-string
Prod = $prodservers.name | out-string
NotInProd = $notinprod.SystemName | out-string
NotInReplication = $NotInReplica.SystemName | out-string
}
$whole | Select-Object prod, replica, NotInReplication, notinprod | Out-file -Path '.\Results\Noprod.csv'
This would be a fine output. Or having them each in their own column.
It appears powershell doesn't want users to export arrays to CSV.
Unless you use Export-CSV instead of Out-File.

Parsing an array of ojects into strings in PowerShell

I am calling Get-AdComputer like this:
[string[]] $computer = Get-ADComputer -properties * -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Where-Object {$_.name -NotLike "*-NONE"} |
Where-Object {$_.name -NotLike "*-ONCALL"} |
Where-Object {$_.name -NotLike "*-BLACKBAUD"} |
Where-Object {$_.name -NotLike "SC-WIN7-1"} |
Where-Object {$_.name -NotLike "UT-SWCLIENT-01"} |
Select-Object Name, LastLogonDate
This creates a string array and I can access it like this:
$computer[1]
which returns
#{Name=W7-9HQPR3J; LastLogonDate=05/08/2017 09:45:13}
I need to output the data to a csv file in two columns Name, LastLogonDate
Unfortunately when I call $Computer | out-file -filepath $ServiceTagsPath -Force -Width 200 -Encoding ascii
I get one column with the headings on each line:
My other requirement is to be able to use the Name column in web-service calls to get warranty information...
so how could I do that?
I would not cast the output to array of strings [string[]] but leave the casting to powershell.
Then you can use command Export-Csv which works better with array of objects.
$computer = Get-ADComputer -properties * -filter {(operatingsystem -like "*Windows 7*")} |
Where-Object {$_.name -like "*-*"} |
Where-Object {$_.name -NotLike "V7-*"} |
Where-Object {$_.name -NotLike "*-NONE"} |
Where-Object {$_.name -NotLike "*-ONCALL"} |
Where-Object {$_.name -NotLike "*-BLACKBAUD"} |
Where-Object {$_.name -NotLike "SC-WIN7-1"} |
Where-Object {$_.name -NotLike "UT-SWCLIENT-01"} |
Select-Object Name, LastLogonDate
$computer | Export-Csv "test.csv" -NoTypeInformation

Add new property based on a different objects property

I'm trying to search through one column in each row of the table. I would then like to add another value to the row based on the number being search.
This code produces the table:
$LUNSSummary = ($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' | foreach {
$Stringdata = $_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$LUNSSummary |
select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups' |
Format-Table -AutoSize
Then I have this code which can search using the "Logical Unit Number" and produce the desired output. In this example the -contains is 1029 from the above screenshot.
$data = $LUNS_in_Pools | Out-String
$pools = $data -replace ': +','=' -split "`r`n`r`n" |
% { New-Object -Type PSCustomObject -Property (ConvertFrom-StringData $_) } |
select -Property *,#{n='LUNs';e={$_.LUNs -split ', '}} -Exclude LUNs
$pools | ? { $_.LUNs -contains 1029 } | select -Expand 'Pool Name'
Which produces in this case "Pool 2". The result can be Pool 1-99.
I want to combine these two codes to search every "Logical Unit Number" and add the result to the end of the table in a 5th section/column "Pools".
EDIT
As requested, raw data:
$NY_LUNS before $LUNSSummary gets it: http://pastebin.com/5wrd51Lf
$LUNS_in_Pools raw data: http://pastebin.com/Zg9q6jhe
Desired Output: (Pool is obtained from "Logical Unit Number")
EDIT 2
This is now the closest to correct so far, it prints the same pool result every time.
$LUNSSummary =
($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' |
foreach { $Stringdata =
$_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$data = $LUNS_in_Pools | Out-String
$pools = $data -replace ': +','=' -split "`r`n`r`n" |
% { New-Object -Type PSCustomObject -Property (ConvertFrom-StringData $_) } |
select -Property *,#{n='LUNs';e={$_.LUNs -split ', '}} -Exclude LUNs
$poolProperty = #{Label="Pool";Expression={$pools | ? { $_.LUNs -contains [int]$_.'LOGICAL UNIT NUMBER'} | select -Expand 'Pool Name'}}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
if I check the output of $pools | ? { $_.LUNs -contains [int]$_.'LOGICAL UNIT NUMBER'} | select -Expand 'Pool Name'
I only see one result. I'm thinking maybe it has to be looped some how?
From the guess of it you just need one more calculated property on the end there for 'Pool'. You already have, and tested, the logic. Just need to implement it.
$poolProperty = #{Label="Pool";Expression={
$lunID = $_.'LOGICAL UNIT NUMBER';
$pools | Where-Object{$_.LUNs -contains $lunID} |
Select-Object -Expand 'Pool Name'}
}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
We take the LOGICAL UNIT NUMBER of the current item in the pipeline and save it so that we can start another to extract the match from the $pools object. As long as you luns are exclusive this would always return one Pool Name.
The above should work but I changed how $pools was created so it matched the logic of $LUNSSummary. I used here-strings for the raw data from your paste bin.
$LUNSSummary = ($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' |
foreach { $Stringdata =
$_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$pools = ($LUNS_in_Pools | Out-String) -split '\s+(?=Pool Name)' | ForEach-Object{
New-Object -Type PSCustomObject -Property (ConvertFrom-StringData ($_ -replace ":","=")) |
Select -Property *,#{n='LUNs';e={$_.LUNs -split ',\s*'}} -Exclude LUNs
}
$poolProperty = #{Label="Pool";Expression={
$lunID = $_.'LOGICAL UNIT NUMBER';
$pools | Where-Object{$_.LUNs -contains $lunID} |
Select-Object -Expand 'Pool Name'}
}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
Looks like $LUNS_in_Pools was a newline delimited string. Piping to Out-String cleaned it up to remove the newlines and allow the regex/ConvertFrom-StringData to work.

Find items which are in array1 but NOT in array2

I extracted two lists of computers from two different tools, Array1 and Array2.
Now I need to extract the ones which are in Array1, but not in Array2.
I managed to get all the matching ones by doing this:
$matchingComp = #()
foreach ($SCCMcomputer in $SCCMcomputers) {
foreach ($eWPTcomputer in $eWPTcomputers) {
if ($SCCMcomputer.Computername -eq $eWPTComputer.Computername) {
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $SCCMcomputer.Computername
$matchingComp +=$obj
}
}
}
$matchingComp | Export-Csv $inEWPT -Delimiter "," -NoTypeInformation
But I still need the ones that are in $SCCMcomputer but NOT in $eWPTcomputers...
I've found some solutions on SO with other languages (e.g. Perl) but not for PowerShell.
UPDATE
I still don't get the correct output, in Excel with this formula:
the output looks like:
means some are here, some not. The output in powershell is like this
means 0KB is emtpy.
$SCCMcomputers | Export-Csv $sccmexport -Delimiter "," -NoTypeInformation
$eWPTcomputers | Export-Csv $ewptexport -Delimiter "," -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "=>"} |select inputobject | Export-Csv $inEWPT -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "=="} |select inputobject | Export-Csv $inBoth -NoTypeInformation
Compare-Object -ReferenceObject $SCCMcomputers -DifferenceObject $eWPTcomputers | ?{$_.sideIndicator -eq "<="} |select inputobject | Export-Csv $inSCCM -NoTypeInformation
And both Column Name (or what it's called) from SCCMcomptuers/eWPTcomputers is "Computername"
Any idea what I could be doing wrong? Both computer arrays are generated from SQL and in hashtables (I think it's called): #{Computername=......}#{Computername...., something like this.
Update 2
foreach ($t in $sccmComputers) {
$Test1 += $t.computername
}
$Test2 = #()
foreach ($t in $ewptComputers) {
$Test2 += $t.computername
}
By removing the Header of the Hashtable and just having arrays full of strings works fantasctic..... even -Property computername did not work... :S
use compare-object cmdlet
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt | ?{$_.sideIndicator -eq "<="} |select inputobject
example :
$sccm=#(1,2,3)
$wpt=#(2,4)
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt -IncludeEqual
will output :
InputObject SideIndicator
2 ==
4 =>
1 <=
3 <=
that means value "2" is on both objects, "1" and "3" only on "the left side" (ie the reference object), while "4" is only on the difference object
Use compare-object as follows
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt -passthru
This should give you just the objects in $sccm but not in $wpt.
CORRECTION:
The above code WILL work for the case where DifferenceObject is guaranteed to be a subset of ReferenceObject. It will FAIL, though, if there are additional objects in DifferenceObject that are not also present in ReferenceObject. The above code returns any objects which are present in EITHER ReferenceObject OR DifferenceObject but NOT in both.
To properly return ONLY objects in ReferenceObject that are not also present in DifferenceObject, the following code is required:
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt |
Where-Object { $_.SideIndicator -eq '<=' } |
ForEach-Object { Write-Output $_.InputObject }
The where-object clause ensures only objects that are present in ReferenceObject are passed down the pipeline.
The foreach-object clause forces the output back to a simple array (ref: Converting Custom Object arrays to String arrays in Powershell - thanks Keith)
You can use -contains and -notcontains
$A1 ="asd","zxc","qwe",'a'
$A2 = "asd","zxc","bqwe",'b'
$A1,$A2 |
%{
$_|
%{
If ($A1 -contains $_ -and $A2 -notcontains $_) {$_}
}
}
The Compare-Object method is indeed the best. What hasn't been addressed yet is clean output to your Excel file.
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Export-Csv sccm-only.csv -NoTypeInformation
will produce two columns. One with "InputObject" and your computer names, and another column with "SideIndicator" and a bunch of rows with "<=".
The easy fix is to select only the column you want:
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Select-Object InputObject | Export-Csv sccm-only.csv -NoTypeInformation
This will give you a single column labeled "InputObject" and your computer names.
If you want to change the column label, use the method from another thread, Windows Powershell Rename Column Heading CSV file:
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '<=' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation
Also, simply change the SideIndicator comparison to get those computers in both systems, or only in eWPT:
# Only in eWPT
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '=>' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation
# In both SCCM and eWPT
Compare-Object $sccm $ewpt | ?{ $_.SideIndicator -eq '==' | Select-Object #{ expression={$_.InputObject}; label='ComputerName' } | Export-Csv sccm-only.csv -NoTypeInformation
$sccm=#(1,2,2,3)
$wpt=#(2,4)
Compare-Object -ReferenceObject $sccm -DifferenceObject $wpt |
Where-Object { $_.SideIndicator -eq '<=' } |
ForEach-Object { Write-Output $_.InputObject }
This will return 1,2,3, this method is not correct

Compare Patch Levels across Multiple servers

Hi all I was working on a script to compare a list of patch levels between multiple servers and show the list of patches missing on either of server. The script should compare between each server within the array and give the output i was trying using Get-Hotfix and also using compare-object to compare and get the server name evaluating $_.sideindicator -match "=>" and $_.sideindicator -match "<=".
can anyone please help further?
here's the code till now for four servers, if there are n number of servers i wanted the logic on how to proceed.
$array=#()
$serd1 = Get-HotFix -ComputerName serd1 | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName serd2 | select -ExpandProperty hotfixid
$serd3 = Get-HotFix -ComputerName serd3 | select -ExpandProperty hotfixid
$serd4 = Get-HotFix -ComputerName serd4 | select -ExpandProperty hotfixid
$check1 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd2 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$check2 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check2 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check2 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check3 = Compare-Object -ReferenceObject $serd1 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check3 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check3 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check4 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd3 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check4 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check4 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$check5 = Compare-Object -ReferenceObject $serd2 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check5 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check5 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$check6 = Compare-Object -ReferenceObject $serd3 -DifferenceObject $serd4 -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$array += $check6 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd3"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check6 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd4"}},#{l="MissingPatches";e={$_.inputobject}}
$array
The question is how to make this script work for random number of servers
This question is old but thought this might help out if anyone else finds this and thinks it is something they'd want to give a go.
Anyway, to meet the requirements stated here, see if this helps - let me know if anyone finds problems:
$Servers = "SERVER1", "SERVER2", "SERVER3", "SERVER4"
$MissingPatches=#()
$ServerPatches = New-Object 'object[]' $($Servers.Length)
for($i=0; $i -lt $($Servers.Length); $i++)
{
$ServerPatches[$i] = Get-HotFix -ComputerName $Servers[$i] | select -ExpandProperty hotfixid
}
for($i=0; $i -lt $($Servers.Length); $i++)
{
for($j=($i+1); $j -lt $($Servers.Length); $j++)
{
$Compare = Compare-Object -ReferenceObject $ServerPatches[$i] -DifferenceObject $ServerPatches[$j] -IncludeEqual | ?{$_.sideindicator -notmatch '=='}
$MissingPatches += $Compare | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={$Servers[$i]}},#{l="MissingPatches";e={$_.inputobject}}
$MissingPatches += $Compare | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={$Servers[$j]}},#{l="MissingPatches";e={$_.inputobject}}
}
}
$MissingPatches = foreach($MissingPatch in $MissingPatches) {
if($($MissingPatch.MissingPatches) -ne "File 1")
{
$MissingPatch
}
}
$MissingPatches | ft -AutoSize
Going down this path I started thinking of potential improvements. So, if I have time I might rewrite and create a patch object that indicates on what server it is present and where it is missing for more detail of why a patch is indicated as "Missing".
When a patch is found on any server it is added to that array. A property of PresentOn will be updated with the server name it was found and the servers so far that didn't contain the patch can be added to a property of MissingOn, then checked continuously going forward. Then, a table of all patches can be displayed by these statuses vs. printing out only a "Missing" array. Could take some doing but might be worth the work. I'm also seeing this being a potentially costly script based on the number of patches in play and servers. But, if anyone else gets to it first (or finds flaws in the approach) let me know here!
This should work:
$servers = "one", "two", "three"
$array = #()
for ($i = 0; $i -lt ($servers.Count - 1); $i++)
{
$serd1 = Get-HotFix -ComputerName $servers[$i] | select -ExpandProperty hotfixid
$serd2 = Get-HotFix -ComputerName $servers[$i+1] | select -ExpandProperty hotfixid
$array += $check1 | ?{$_.sideindicator -match "=>"} | Select-Object #{l="HostName";e={"serd1"}},#{l="MissingPatches";e={$_.inputobject}}
$array += $check1 | ?{$_.sideindicator -match "<="} | Select-Object #{l="HostName";e={"serd2"}},#{l="MissingPatches";e={$_.inputobject}}
}
$array

Resources