Compare-Object Not Comparing Each Element of Array - powershell

I am attempting to use Compare-Object to find elements that are in one array ($fileArray) but not in another ($dictionaryArray). For some reason, Compare-Object is only comparing the last two elements in fileArray. Here is the code I am trying to run.
$dictionary = Get-Content "C:\Users\Joe\Documents\PowerShell Scripts\Work\PCI Numbers\dictionary.txt"
$file = Get-Content "C:\Users\Joe\Documents\PowerShell Scripts\Work\PCI Numbers\file.txt"
$dictionaryArray = #()
$fileArray = #()
$dictionaryLength = $dictionary | Measure-Object -Line
$fileLength = $file | Measure-Object -Line
$i = 0
while($i -lt $dictionaryLength.Lines){
$name = $dictionary | Select -Index $i
$i++
while($dictionary[$i] -ne " " -and $i -lt $dictionaryLength.Lines){
$dictionaryObject = New-Object -TypeName PSObject
$dictionaryObject | Add-Member -Name 'PC' -MemberType Noteproperty -Value $name
$dictionaryObject | Add-Member -Name 'File' -MemberType Noteproperty -Value $dictionary[$i]
$dictionaryArray += $dictionaryObject
$i++
}
$i++
}
$i = 0
while($i -lt $fileLength.Lines){
$name = $file | Select -Index $i
$i++
while($file[$i] -ne " " -and $i -lt $fileLength.Lines){
$fileObject = New-Object -TypeName PSObject
$fileObject | Add-Member -Name 'PC' -MemberType Noteproperty -Value $name
$fileObject | Add-Member -Name 'File' -MemberType Noteproperty -Value $file[$i]
$fileArray += $fileObject
$i++
}
$i++
}
$i = 0
Compare-Object -ReferenceObject $fileArray -DifferenceObject $dictionaryArray |
Where-Object {$_.SideIndicator -eq '<='} |
ForEach-Object {Write-Output $_.InputObject}
When I simply run Compare-Object -ReferenceObject $fileArray
-DifferenceObject $dictionaryArray, I get the output
InputObject SideIndicator
#{PC=Joe; File=nopethere} <=
#{PC=Joe; File=hi!!!#} <=
There are more items than this in the fileArray. When I run $fileArray, I get
PC File
Eric I like to
Eric there
Joe code because
Joe hello
Joe but why?
Joe *no\thank/ you :c
Joe nopethere
Joe hi!!!#
Why is my code not comparing each one of these objects?
EDIT
Here is dictionary.txt
Eric
because
hello there
Joe
but why?
*no\thank/ you :c
nope
Here is file.txt
Eric
I like to
there
Joe
code because
hello
but why?
*no\thank/ you :c
nopethere
hi!!!#
I use my loop to create objects. The first line of each paragraph is the "PC" for each subsequent object. Then, each line is the "File" of each object. I want to check which objects are in fileArray and not in dictionaryArray. I would expect the output to be
PC File
Eric I like to
Joe code because
Joe hello
Joe nopethere
Joe hi!!!#
Any help would be appreciated!

Related

Incrementing value in a loop in powershell

I would like to ask for some help regarding an incrementing value applied in the script below
For each certificate it found, a counter increments by 1, I tried usig $i=0; $i++ but I'm not getting anywhere.
$Expiry = Get-ChildItem -Path cert: -Recurse
$Rep= #()
Foreach ($cert in $Expiry)
{
if ($cert.notafter -le (get-date).Adddays(120) -AND $cert.notafter -gt (get-date))
{
$obj = New-Object PSObject
$Daysleft = $Cert.NotAfter - (get-date)
$obj | Add-Member -type NoteProperty -Name "Path" $cert.PSParentPath
$obj | Add-Member -type NoteProperty -Name "Issuer" $cert.Issuer
$obj | Add-Member -type NoteProperty -Name "NotAfter" $cert.NotAfter
$obj | Add-Member -type NoteProperty -Name "DaysLeft" $Daysleft.Days
$Rep +=$obj
}
}
My goal here is if it satisfies the condition, it will display the certificate and a counter will be plus 1. until it completes the loop then it displays the total certificates found
Hoping for your help
Thank you
You don't need a loop counter for this, or even a loop if you use Select-Object to return objects with the chosen properties like this:
# It's up to you, but personally I would use $today = (Get-Date).Date to set this reference date to midnight
$today = Get-Date
$Expiry = Get-ChildItem -Path cert: -Recurse
$Rep = $Expiry | Where-Object { $_.NotAfter -le $today.AddDays(120) -and $_.NotAfter -gt $today |
Select-Object #{Name = 'Path'; Expression = {$_.PSParentPath}},
Issuer, NotAfter,
#{Name = 'DaysLeft'; Expression = {($_.NotAfter - $today).Days}}
}
# to know howmany items you now have, use
#($Rep).Count
Note:
I'm using calculated properties in the Select-Object line to get the properties you need
By surrounding $Rep in the last line with #(), you are forcing the variable to be an array, so you can use its .Count property safely

Find and Replace powershell script not functioning, not sure where else to go

I am fairly new to powershell scripting, so please bear with me. I am trying to write a script that looks for a legacy vulnerability id inside a column on a csv, and then looks inside a sub directory at some other powershell scripts, matches the legacy ID number on the scripts inside of that subdirectory, and replaces them with the new vulnerability ID from the first column on the csv. I probably did not explain that very well but I can eleborate. Below is what I currently have
**$list = Import-csv -Path .\Downloads\test.csv
Foreach($PSScript in (Get-ChildItem -Path .\Downloads\Scripts -Filter "*.ps1" -Recurse)){
$contents = Get-Content -Path $PSScript.FullName -Raw
foreach($item in (($list.Legacy -split ";").trim())){
if($contents -match $item){
$contents.Replace($item,($list.'Vuln ID'[$list.Legacy.IndexOf($item)]))
Write-Host $contents
}
}
}**
and this is the output
**$GroupID = "V-44745"
$Hostname = (Get-WmiObject Win32_ComputerSystem).Name
$Title = "Google Chrome Current Windows STIG"
if ($Hostname -eq $null){
$Hostname = $env:computername}
$Configuration = ""
$Regkey = (Get-ItemProperty Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome").AllowOutdatedPlugins
if ($Regkey -eq '0'){
$Configuration = 'Completed'}
else{
$Configuration = 'Ongoing'}
$Audit = New-Object -TypeName System.Object
$Audit | Add-Member -MemberType NoteProperty -Name GroupID -Value $GroupID
$Audit | Add-Member -MemberType NoteProperty -Name Hostname -Value $Hostname
$Audit | Add-Member -MemberType NoteProperty -Name Configuration -Value $Configuration
$Audit | Add-Member -MemberType NoteProperty -Name Title -Value $Title
$Audit**
The $Group ID above is the value that I am trying to change
I do not know how to add the csv values in just text and keep the formatting,
I tried, but here is a picture CSV Value
So my goal is it should change $GroupID = "V-44711" to $GroupID = "V-221558". I think because I am splitting $item it is not finding it in the array.
I tried what TheMadTechnician suggested, and for some reason it started creating empty lines continuously at the end of the script. I changed
Write-Host | Set-Content
to
Write-Host $contents
just to display something else.
It returns with
$GroupID = "V-44711"
$Hostname = (Get-WmiObject Win32_ComputerSystem).Name
$Title = "Google Chrome Current Windows STIG"
if ($Hostname -eq $null){
$Hostname = $env:computername}
$Configuration = ""
$Regkey = (Get-ItemProperty Registry::"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome").AllowOutdatedPlugins
if ($Regkey -eq '0'){
$Configuration = 'Completed'}
else{
$Configuration = 'Ongoing'}
$Audit = New-Object -TypeName System.Object
$Audit | Add-Member -MemberType NoteProperty -Name GroupID -Value $GroupID
$Audit | Add-Member -MemberType NoteProperty -Name Hostname -Value $Hostname
$Audit | Add-Member -MemberType NoteProperty -Name Configuration -Value $Configuration
$Audit | Add-Member -MemberType NoteProperty -Name Title -Value $Title
$Audit
I have also tried something like this
ForEach($Vuln in $List){
($Vuln.Legacy -split ";").trim() | ForEach-Object {if($contents -match $_){$contents -Replace $_,$Vuln.'Vuln ID' | Out-Null;Write-host $contents;Write-Host $_}}
It seems to be finding the value in the legacy column just fine. The replace seems to not be working
Shoutout to TheMadTechnician for setting me on the right path. My second attempt worked I just forgot to set $contents in the second half. If TheMadTechnician comments back, I would still very much be interested in finding out why that didn't work. this was my Solution
$list = Import-csv -Path "Downloads\test.csv"
Foreach($PSScript in (Get-ChildItem -Path "Downloads\Win10-STIG-Audit" -Filter "*.ps1" -Recurse)){
$contents = Get-Content -Path $PSScript.FullName -Raw
ForEach($Vuln in $List){
($Vuln.Legacy -split ";").trim() | ForEach-Object {if($contents -match $_ -and $_ -notmatch "null"){$contents.Replace($_,$Vuln.'Vuln ID') | Out-File $PSScript.FullName; break}}
}
}
You're right, because you're splitting the value it's not finding it in the array. But I think there's a better way to do this! My suggestion is to use RegEx to do the replace, and build your matching pattern per updated vuln. Something like this:
ForEach($Vuln in $List){
$Legacy = ($Vuln.Legacy -split ';' | ForEach-Object {[regex]::escape($_.trim())}) -join '|'
$Updated = $Contents -replace $Legacy, $Vuln.'Vuln ID'
$Updated | Set-Content $PSScript.FullName
Write-Host | Set-Content
}
What that does, is the first line in the CSV it will look at the Legacy column, split it on the semicolon, then for each item in that it will trim the string, and escape it for a RegEx search, then join all strings from the split with a pipe. That comes out looking like this:
V-44711|SV-57545
The way that works in a -replace is that it will match on either V-44711, or SV-5745, and replace it with $Vuln.'Vuln ID' (the updated value). Since we're splitting within the ForEach, and including the entire object not just one property we can reference other properties more easily.
Edit: The above is dumb, and won't work. It's seriously writing the file each time it loops through each Legacy. So even if it finds and updates the item it will just overwrite it with the old contents on the next loop iteration. Solution to that is to change $Updated to $Contents, and probably move the output to after the loop.
ForEach($Vuln in $List){
$Legacy = ($Vuln.Legacy -split ';' | ForEach-Object {[regex]::escape($_.trim())}) -join '|'
$Contents = $Contents -replace $Legacy, $Vuln.'Vuln ID'
Write-Host | Set-Content
}
$Contents | Set-Content $PSScript.FullName

Get PSObject array size or count

I create an array like this:
$Array = #()
$Item = New-Object PSObject
$Item | Add-Member -Type NoteProperty -Name item1 -Value test
$Item | Add-Member -Type NoteProperty -Name item2 -Value test
$Array += $Item
Now I want to add a check to determine if $Item is empty before adding it in $Array. How can I get the member count of $Item ?
I tried stuff like :
$Item.count
$Item.length
#($Item).count
($Item | Measure).count
($Item | Get-Member).count
$Item.psobject.members.count
But none of them gives me the actual member count.
You can use the hidden .PsObject.Properties to either check for
$Item.PSobject.Properties.Value.count or
$Item.PSobject.Properties.Names.count
$Item = New-Object PSObject
$Item.Psobject.Properties.value.count
0
$Item | Add-Member -Type NoteProperty -Name item1 -Value test
$Item.Psobject.Properties.value.count
1
$Item | Add-Member -Type NoteProperty -Name item2 -Value test
$Item.Psobject.Properties.value.count
2
The correct way is:
($Item|Get-Member -Type NoteProperty).count
The following Get_ItemCount function could help:
Function Get_ItemCount {
$aux = $($item | Get-Member -MemberType NoteProperty)
if ( $aux -eq $null ) {
0
} elseif ( $aux -is [PSCustomObject] ) {
1
} else {
$aux.Count
}
}
$Item = New-Object PSObject
Get_ItemCount # 0
$Item | Add-Member -Type NoteProperty -Name item1 -Value test
Get_ItemCount # 1
$Item | Add-Member -Type NoteProperty -Name item2 -Value test
Get_ItemCount # 2
Output
PS D:\PShell> .\SO\55064810.ps1
0
1
2
PS D:\PShell>

Sum Columns Using Powershell

I have written the following PowerShell script for getting disk space information for servers in our environment.
$servers = Get-Content E:\POC.txt
$array = #()
foreach($server in $servers){
$sysinfo = Get-WmiObject Win32_Volume -ComputerName $server
for($i = 0;$i -lt $sysinfo.Count; $i++){
$sname = $sysinfo[$i].SystemName
$servername = $server
$label = $sysinfo[$i].Label
if(($label) -and (!($label.Contains("FILLER")))){
write-host "Processing $label from $server"
$name = $sysinfo[$i].Name
$capacity = [math]::round(($sysinfo[$i].Capacity/1GB),2)
$fspace = [math]::round(($sysinfo[$i].FreeSpace/1GB),2)
$sused = [math]::round((($sysinfo[$i].Capacity - $sysinfo[$i].FreeSpace)/1GB),2)
$fspacepercent = [math]::Round((($sysinfo[$i].FreeSpace*100)/$sysinfo[$i].Capacity),2)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "SystemName" -Value $sname
$obj | Add-Member -MemberType NoteProperty -Name "ServerName" -Value $server
$obj | Add-Member -MemberType NoteProperty -Name "Label" -Value $label
$obj | Add-Member -MemberType NoteProperty -Name "Name" -Value $name
$obj | Add-Member -MemberType NoteProperty -Name "Capacity(GB)" -Value $capacity
$obj | Add-Member -MemberType NoteProperty -Name "FreeSpace(GB)" -Value $fspace
$obj | Add-Member -MemberType NoteProperty -Name "Used(GB)" -Value $sused
$obj | Add-Member -MemberType NoteProperty -Name "FreeSpace%" -Value $fspacepercent
$array += $obj
}
}
$array += write-output " "
$totalSize = ($array | Measure-Object 'Capacity(GB)' -Sum).Sum
$array += $totalsize
$array += write-output " "
}
$filename = "E:\VolumeReport.csv"
$array | Export-CSV $filename -NoTypeInformation
One additional requirement here is to get the sum of the columns for Capacity, Size and Freespace for each server. I tried using Measure-Object but no success.
No values are getting outputted here. Just blank. Please look into this and kindly assist.
Let try this on for size shall we.
$servers = Get-Content E:\POC.txt
$propertyOrdered = "SystemName","ServerName","Label","Name","Capacity(GB)","FreeSpace(GB)","Used(GB)","FreeSpace%"
$filename = "C:\temp\VolumeReport.csv"
('"{0}"' -f ($propertyOrdered -join '","')) | Set-Content $filename
foreach($server in $servers){
$sysinfo = Get-WmiObject Win32_Volume -ComputerName $server
$serverDetails = #()
for($i = 0;$i -lt $sysinfo.Count; $i++){
$sname = $sysinfo[$i].SystemName
$servername = $server
$label = $sysinfo[$i].Label
if(($label) -and (!($label.Contains("FILLER")))){
write-host "Processing $label from $server"
$name = $sysinfo[$i].Name
$capacity = [math]::round(($sysinfo[$i].Capacity/1GB),2)
$fspace = [math]::round(($sysinfo[$i].FreeSpace/1GB),2)
$sused = [math]::round((($sysinfo[$i].Capacity - $sysinfo[$i].FreeSpace)/1GB),2)
$fspacepercent = [math]::Round((($sysinfo[$i].FreeSpace*100)/$sysinfo[$i].Capacity),2)
$props = #{
"SystemName" = $sname
"ServerName" = $server
"Label" = $label
"Name" = $name
"Capacity(GB)" = $capacity
"FreeSpace(GB)" = $fspace
"Used(GB)" = $sused
"FreeSpace%" = $fspacepercent
}
# Build this server object.
$serverDetails += New-Object PSObject -Property $props
}
}
# Output current details to file.
$serverDetails | Select $propertyOrdered | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Add-Content $filename
#Calculate Totals and append to file.
$totals = '"","","","Totals",{0},{1},{2},""' -f ($serverDetails | Measure-Object -Property "Capacity(GB)" -Sum).Sum,
($serverDetails | Measure-Object -Property "FreeSpace(GB)" -Sum).Sum,
($serverDetails | Measure-Object -Property "Used(GB)" -Sum).Sum
$totals | Add-Content $filename
}
Part of the issue here is that you were mixing object output and static string output which most likely would have been holding you back. I tidied up the object generation in a way that should be 2.0 compliant. Not that what you were going was wrong in anyway but this is a little more pleasing to the eye then all the Add-Members
I removed $array since it did not have a place anymore since the logic here is constantly output data to the output file as supposed to storing it temporarily.
For every $server we build an array of disk information in the variable $serverDetails. Once all the disks have been calculated (using your formulas still) we then create a totals line. You were not really clear on how you wanted your output so I guessed. The above code should net output like the following. (It looks a lot nicer in Excel or in a csv aware reader. )
"SystemName","ServerName","Label","Name","Capacity(GB)","FreeSpace(GB)","Used(GB)","FreeSpace%"
"server01","server01","System Reserved","\\?\Volume{24dbe945-3ea6-11e0-afbd-806e6f6e6963}\","0.1","0.07","0.03","71.85"
"","","","Totals",0.1,0.07,0.03,""
"server02","server02","System Reserved","\\?\Volume{24dbe945-3ea6-11e0-afbd-806e6f6e6963}\","0.1","0.07","0.03","69.27"
"server02","server02","images","I:\","1953.12","152.1","1801.02","7.79"
"server02","server02","Data","E:\","79.76","34.59","45.18","43.36"
"","","","Totals",2032.98,186.76,1846.23,""

Want to Wrap PSList in a Powershell function to use pipeline values

I like PSList, and use the CPU and Elapsed times to indentify processes that need to be killed. I would like to wrap it in a powershell function that returns pipeline values so that I could do something like the following:
get-remoteprocess server1 | where {$_.CPUMinutes -gt 4}
I get stuck in returning values from the function - which I understand would need to be an array of objects.
Heres what my first version looks like:
function get-remoteproc {
param ($hostname)
$results = pslist "\\$hostname"
for ($i= 3; $i -le $results.length; $i++) {
$strline = $results[$i]
$StrWithPrefix = " "+$results[$i]
$trimmedline = $StrWithPrefix -replace '\s+', " ";
$Splitline = $trimmedline.Split(" ")
$ProcessName = $Splitline[1]
.
.
$ProcessCPUTime = ":"+[string]$Splitline[7]
$SplitCpuTime = $ProcessCPUTime.Split(":")
$CpuHours = $SplitCpuTime[1]
$CpuMinutes = $SplitCpuTime[2]
$CpuSeconds = $SplitCpuTime[3]
.
.
.
$obj = New-Object PSObject
$obj | Add-Member Noteproperty -Name "Name" -Value $Name
$obj | Add-Member Noteproperty -Name "PPid" -Value $Ppid
$obj | Add-Member Noteproperty -Name "Pri" -Value $Pri
}
Taking Doug Finke's suggestion, which is pleasingly terse, here is my slightly adapted version, which works well with pipeline values.
function get-remoteproc {
param ($hostname=$env:COMPUTERNAME)
$results = PsList "\\$hostname"
foreach($record in $results[3..($results.Count)]) {
# Remove spaces
while ($record.IndexOf(" ") -ge 0) {
$record = $record -replace " "," "
}
$Name,$Processid,$Pri,$Thd,$Hnd,$Priv,$CPUTime,$ElapsedTime = $record.Split(" ")
$properties = #{
Name = $Name
Pid = $Processid
Pri = $Pri
Thd = $Thd
Hnd = $Hnd
Priv = $Priv
CPUTime = $CPUTime
ElapsedTime = $ElapsedTime
}
New-Object PSObject -Property $properties |
Add-Member -PassThru ScriptProperty CPUH {[int]$this.CPUTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty CPUM {[int]$this.CPUTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty CPUS {[int]$this.CPUTime.Split(":")[2]} |
Add-Member -PassThru ScriptProperty ElaH {[int]$this.ElapsedTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty ElaM {[int]$this.ElapsedTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty ElaS {[int]$this.ElapsedTime.Split(":")[2]}
}
}
And a call to the function, which shows that the objects are unrolled correctly for consumption by the pipeline:
get-remoteproc "Server1" | where {(($_.CPUM * $_.CPUS) -gt 60) -and ($_.name -notmatch "Idle" )}|
ft name, pid, pri, thd, hnd, priv, CPUH, cpuM, cpuS, ElaH, ElaM, ElaS -auto
Thanks, everyone!
As empo points out, you need to emit the $obj back to the pipeline. Here is another way to work the plist text into PowerShell objects.
function get-remoteproc {
param ($hostname=$env:COMPUTERNAME)
$results = PsList "\\$hostname"
foreach($record in $results[3..($results.Count)]) {
# Remove spaces
while ($record.IndexOf(" ") -ge 0) {
$record = $record -replace " "," "
}
$Name,$Processid,$Pri,$Thd,$Hnd,$Priv,$CPUTime,$ElapsedTime = $record.Split(" ")
$properties = #{
Name = $Name
Pid = $Processid
Pri = $Pri
Thd = $Thd
Hnd = $Hnd
Priv = $Priv
CPUTime = $CPUTime
ElapsedTime = $ElapsedTime
}
New-Object PSObject -Property $properties |
Add-Member -PassThru ScriptProperty CPUHours {$this.CPUTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty CPUMinutes {$this.CPUTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty CPUSeconds {$this.CPUTime.Split(":")[2]}
}
}
To return an array of values passed to the pipeline one by one, you just need to return multiple values in your function. For example:
function get-remoteproc {
param ($hostname)
$results = pslist "\\$hostname"
for ($i= 3; $i -le $results.length; $i++) {
# your staff to $obj
# output $obj
$obj
}
}
The function result is an array of $obj. When connected to a pipeline the array will be enrolled, and all the values will be passed to the stream one by one.