PowerShell Compare-Object SideIndicator
I have this code, which its output is not as intended
Compare-Object $(Get-Content c:\Folder\List1.txt) $(Get-Content
c:\Folder\List2.txt) |
Out-File C:\Folder\filename.txt
I only want to get the " => SideIndicator " Values only not both . I.e what is different in list2
Use the Where-Object cmdlet to filter by the SideIndicator property (PSv3+ syntax):
Compare-Object (Get-Content c:\Folder\List1.txt) (Get-Content c:\Folder\List2.txt) |
Where-Object SideIndicator -eq '=>' |
Out-File C:\Folder\filename.txt
Also note how just (...) is sufficient to provide a single command or expression as an argument - no need for $(...).
Note:
Filtering by => outputs only those lines that are unique to the RHS (-DifferenceObject) collection; the SideIndicator property value (which is a string) points in the direction of the collection that a given object is unique to (assuming positional argument passing):
<= ... unique to the LHS (implied -ReferenceObject parameter)
=> ... unique to the RHS (implied -DifferenceObject parameter)
== ... present in both collections (only with -IncludeEqual)
However, on output Compare-Object wraps the original objects by default, namely in a custom object that has a .SideIndicator property and an .InputObject property, the latter containing the original input object, which results in output such as:
InputObject SideIndicator
----------- -------------
unique line 1 from List2.txt =>
unique line 2 from List2.txt =>
To save just the lines in the output file, add the -PassThru switch to the Compare-Object call.
Note: The reason that filtering by the .SideIndicator property still works in this case is that with -PassThru the original objects are decorated with this property, using PowerShell's ETS (Extended Type System).
For strings, this extra property is ignored when they're saved to a file, such as with Out-File; however, in other scenarios it may surface; to prevent this:
Do not use -PassThru
Add an additional pipeline segment that pipes the Where-Object output to
ForEach-Object InputObject, which outputs the undecorated original objects.
Related
When running the following line in PowerShell including the "Format-Table -AutoSize", an empty output file is generated:
Get-ChildItem -Recurse | select FullName,Length | Format-Table -AutoSize | Out-File filelist.txt
The reason I need the output file to be AutoSized is because longer filenames from the directoy are being trunacted. I am trying to pull all Filenames and File Sizes for all files within a folder and subfolders. When removing the -Autosize element, an output file is generated with truncated file names:
Get-ChildItem -Recurse | select FullName,Length | Out-File filelist.txt
Like AdminOfThings commented, use Export-CSV to get the untruncated values of your object.
Get-ChildItem -Recurse | select FullName,Length | Export-CSv -path $myPath -NoTypeInformation
I do not use Out-File much at all, and only use Format-Table/Format-List for interactive scripts. If I want to write data to a file, Select-Object Column1,Column2 | Sort-Object Column1| Export-CSV lets me select the properties of the object I am exporting that I want to export, and sort the records as needed. you can change the delimiter from a comma to tab/pipe/whatever else you may need.
While the other answer may address the issue, you may have other reasons for wanting to use Out-File. Out-File has a "Width" parameter. If this is not set, PowerShell defaults to 80 characters - hence your issue. This should do the trick:
Get-ChildItem -Recurse | select FullName,Length | Out-File filelist.txt -Width 250 (or any other value)
The Format-* commandlets in PowerShell are only intended to be used in the console. They do not actually produce output that can be piped to other commandlets.
The usual approach to get the data out is with Export-Csv. CSV files are easily imported into other scripts or spreadsheets.
If you really need to output a nicely formatted text file you can use .Net composite formatting with the -f (format) operator. This works similarly to printf() in C. Here is some sample code:
# Get the files for the report
$files = Get-ChildItem $baseDirectory -Recurse
# Path column width
$nameWidth = $files.FullName |
ForEach-Object { $_.Length } |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
# Size column width
$longestFileSize = $files |
ForEach-Object { $_.Length.tostring().Length } |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
# Have to consider that some directories will have no files with
# length strings longer than "Size (Bytes)"
$sizeWidth = [System.Math]::Max($longestFileSize, "Size (Bytes)".Length)
# Right-align paths, left-align file size
$formatString = "{0,-$nameWidth} {1,$sizeWidth}"
# Build the report and write it to a file
# ArrayList are much more efficient than using += with arrays
$lines = [System.Collections.ArrayList]::new($files.Length + 3)
# The [void] cast are just to prevent ArrayList.add() from cluttering the
# console with the returned indices
[void]$lines.Add($formatString -f ("Path", "Size (Bytes)"))
[void]$lines.Add($formatString -f ("----", "------------"))
foreach ($file in $files) {
[void]$lines.Add($formatString -f ($file.FullName, $file.Length.ToString()))
}
$lines | Out-File "Report.txt"
I thought this would be a pretty simple undertaking - a program that returns webrequest elements and sorts them by a custom property which, in this case, is a substring "extracted" from $_.innertext. (Specifically, I want to sort a list of "deals" which are described in various ways by $-amount.)
But even simple string operations on $_.innertext don't seem to work, always yielding an error of this sort.
Sort-Object : You cannot call a method on a null-valued expression.
Here is the code I am working with - I'll appreciate any guidance, whether pointing out code issues or suggesting alternative methods.
# function to scrub text from a Class filtering on keyword, sort by $*
function Get-Deals {
param($keyword)
$url = "https://slickdeals.net/newsearch.php?q=$keyword"
$result = Invoke-WebRequest $url
$result.AllElements |
Where Class -eq "dealTitle" |
Where {($_.innertext -match ($keyword)) -and ($_.innertext -match ("$"))} |
Sort-Object #{Expression={$_.inntertext.substring($_.innertext.IndexOf('$'))}} |
Select -ExpandProperty innerText
}
Get-Deals("TV")
Here's a (partial) solution to the problem I initially asked about. Code below - note these 2 changes:
$_.innertext enclosed in quotation marks in sort statement
some changes to Where criteria to make sure I was filtering adequately to ensure all elements have a '$' in them (previously I was missing some cases).
Here's why I said this is only a "(partial)" solution: Right now my sort is working, but I'm sorting based on strings, not by numbers. So $9.99 will come after $1999.99.
But that's an entirely different problem from what I initially asked about.
function Get-Deals {
param($keyword)
$url = "https://slickdeals.net/newsearch.php?q=$keyword"
$result = Invoke-WebRequest $url
$result.AllElements |
Where Class -eq "dealTitle" |
Where {( ($_.innertext -match ($keyword)) -and ($_.innertext -like '*$*') )} |
#Where {$_.innertext -like '*$*'} |
Sort-Object #{Expression={"$_.innertext".substring("$_.innertext".IndexOf('$'))}} |
Select -ExpandProperty innerText
}
Get-Deals("TV")
I'd lile to sort a csv in powershell that looks like this:
0.980242;DATABASE_PICTURES/P1040793.JPG
0.167471;DATABASE_PICTURES/P1040805.JPG
0.133792;DATABASE_PICTURES/P1040808.JPG
0.616142;DATABASE_PICTURES/P1040819.JPG
0.0485409;DATABASE_PICTURES/P1040825.JPG
0.17998;DATABASE_PICTURES/P1040826.JPG
0.0428832;DATABASE_PICTURES/P1040837.JPG
0.713708;DATABASE_PICTURES/P7020228.JPG
1;DATABASE_PICTURES//P7020229.JPG
I want to sort the list from highest to lowest number in the first column -->
1;DATABASE_PICTURES//P7020229.JPG
0.980242;DATABASE_PICTURES/P1040793.JPG
0.713708;DATABASE_PICTURES/P7020228.JPG
0.616142;DATABASE_PICTURES/P1040819.JPG
and so on
I tried this script
Import-CSV C:\xampp\htdocs\results\result.csv -Header ('foo', 'bar') -delimiter ';' | Sort-Object foo
but it just doesn't do anything. I've also researched half the internetz, but none of the given codes work or make sense to me. Could somebody give me a hint?
Just to get the answer in here since no one has up to this point.
The problem you are having is not that the sort is not working but that it is sorting alphabettically and not numerically like you would expect.
Foo
---
0.0428832
0.0485409
0.133792
0.167471
0.17998
0.616142
0.713708
0.980242
1
When all properties are imported using Import-CSV they are typed as string. Output from Get-Member
Bar NoteProperty System.String Bar=DATABASE_PICTURES/P1040793.JPG
Foo NoteProperty System.String Foo=0.980242
Depending on the other needs of your code you can simply use #PetSerAI's suggestion and cast it in the sort.
`| Sort-Object {[double]$_.Foo} -Descending`
If you have other things to do with that data it might be an advantage to change its type after the import so that you don't have to do multiple casts.
$data = Import-CSV C:\xampp\htdocs\results\result.csv -Header ('foo', 'bar') -delimiter ';' |
Select-Object -ExcludeProperty Foo #{Name="Foo";Expression={[double]$_.Foo}},*
$data | Sort-Object foo -Descending
I will assume that you have more properties that just foo and bar so to make it so you don't have too retype all the properties you can just -ExcludeProperty the one that needs special attention and then used a Calculated property to cast the variable as [double]. Now you can Sort and get the expected results.
Bar NoteProperty System.String Bar=DATABASE_PICTURES/P1040793.JPG
Foo NoteProperty System.Double Foo=0.980242
I'm having trouble terminating a foreach-object loop in PowerShell v2. For a rough idea of the task I'm trying to accomplish, here's the pseudo-code:
Read lists of host machines from a text file
For each host in the text file get Win32_Product (filtered against an exclusion list),
convert output to html and save.
The reason for the script is that I've amassed a text file listing all applications included on standard client images, and would like to periodically scan hosts from another text file to see if there are any unauthorized, sketchy or otherwise unnecessary applications on the host machines.
The code does work in a rough sense, but the main issue I'm having is that the script will not terminate without manual intervention. I guess the component I'm missing here is to run the loop until some condition exists (ie. first line in the host file is encountered for the second time), then terminates the script. Although this is the method I've envisioned, I am always open to other logic, especially if its more efficient.
Here's the actual code:
Get-Content c:\path\to\testhostlist.txt | Foreach-Object {
Get-WmiObject Win32_Product |
Where-Object { $_.Name -f "'C:\path\to\testauthapplist.txt'" |
ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"}} |
Set-Content c:\path\to\unauthapplisttest.html
I don't see how the first line of the host file (I infer you mean testhostlist.tx) would ever be encountered a second time, since you're only listing it once. This doesn't even seem to be an infinite loop that would need an exit condition. Foreach-Object doesn't repeat indefinitely.
It seems to me that the problem is not that the loop doesn't exit without a condition, it's that the syntax is invalid.
Where-Object filters the pipeline by passing only objects that meet a certain condition, but the scriptblock that follows doesn't perform a boolean test.
In fact, the content of the scriptblock doesn't appear valid in and of itself. -f is the format operator, and takes a format string as the left operand, but $_.Name is not a format string.
I'm going to take a guess here, based on your description, that the idea is to filter the results of Get-WmiObject Win32_Product for objects whose Name property isn't listed in testauthapplist.txt (I take it that's the "exclusion list" you're referring to). If so, this is the correct syntax:
Get-Content c:\path\to\testhostlist.txt | %{
Get-WmiObject Win32_Product | ?{
(Get-Content 'C:\path\to\testauthapplist.txt') -notcontains $_.Name
} | ConvertTo-Html name,vendor,version -title $name -body "<H2>Unauthorized Applications.</H2>"
} | Set-Content c:\path\to\unauthapplisttest.html
(Note that %{} and ?{} are just abbreviations for Foreach-Object and Where-Object, respectively.)
If i understood you correctly you are trying to stop your Script completely? If so did you try Break?
If you only want to skip a loop use continue
$hostlist = Get-Content c:\path\to\testhostlist.txt
$a = #()
Foreach($item in $hostlist)
{
$a += "<style>"
$a += "BODY{background-color:gray;}"
$a += "TABLE{margin: auto;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a += "TH{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:yellow}"
$a += "TD{border-width: 1px;padding: 4px;border-style: solid;border-color: black;background-color:white}"
$a += "h2{color:#fff;}"
$a += "</style>"
Get-WmiObject Win32_Product | select name,vendor,version | sort name | ConvertTo-Html -head $a -body "<Center><H2>Unauthorized Applications.</H2></Center>" | Out-File c:\path\to\$item"-applist.html"
}
I have six .txt files in a directory. So, I create a variable thus:
$foo = gci -Name *.txt
$foo is now an array of six strings. In my case I have
PS > $foo
Extensions.txt
find.txt
found_nots.txt
output.txt
proteins.txt
text_files.txt
PS > $foo.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS > $foo.Count
6
I'd like to measure that object, so I pass it to Measure-Object:
PS > $foo | Measure-Object
Count : 6
Average :
Sum :
Maximum :
Minimum :
Property :
That's what I was expecting. However, I might also pass $foo like this:
PS> Measure-Object -InputObject $foo
Count : 1
Average :
Sum :
Maximum :
Minimum :
Property :
That's not what I was expecting. What's going on here?
When you execute:
$foo | measure-object
PowerShell automatically unrolls collections/arrays and passes each element down the pipeline to the next stage.
When you execute:
measure-object -inputobject $foo
The cmdlet does not internally unroll the collection. This is often times helpful if you want to inspect the collection without having PowerShell do its automatic unrolling. BTW the same thing applies to Get-Member. If you want to see the members on the "collection" instead of each individual element do this:
get-member -inputobject $foo
One way to simulate this in the pipeline case is:
,$foo | Get-Member
This will wrap whatever foo is (collection in this case) in another collection with one element. When PowerShell automatically unrolls that to send elements down the pipeline, the only element is $foo which gets sent down the pipeline.
PowerShell may be able to help you out here. Let's take a look at Get-Help Measure-Object -full and check out that parameter.
-InputObject <psobject>
Specifies the objects to be measured. Enter a variable that contains
the objects, or type a command or expression that gets the objects.
Required? false
Position? named
Default value
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
Pipeline input is accepted by value, so that means that it counts each line of $foo and tallies them. For what it is worth, all of the example usages on Technet and in the Get-Help reference use pipeline input for this parameter.