Outputs and easy algorithm question Object Oriented - powershell

$a = dir
foreach ($file in $a)
{
if (($file.index%2 ) -eq 0)
//Hopefully this function works, supposed to
(Ideally) print every other file
{
Write-Host $file.name
}
}
The function -eq 0... not sure if that prints out every other file. I do not know exactly how the files are numbered, or how you reference a number to the file. Do you treat every file as an object and number them? Then make a function regarding the numbers made appended to the file?
Fairly new to this, I'm used to html, css.
If you have a more proficient answer, I'm open to the idea too.

Your script almost works.
Removed alias for dir, and sorted results as requested.
The -File switch for Get-ChildItem excludes folders. I guess that's what you want, but remove it otherwise.
Since there's not an easy way to get the current position in foreach, I used a for loop instead, but it's the same idea. If you want to try with foreach, you could set a variable to true, and then not (!) it each iteration.
$Path = 'C:\yourpath'
$Files = Get-ChildItem -Path $Path -File |
Sort-Object -Property 'Name' -Descending
for ($i = 0; $i -lt $Files.Count; $i++) {
if ($i % 2 -eq 0) {
Write-Host $Files[$i].Name
}
}
If you're using this output further, it's highly recommended to write results to an object rather than the console window.

Why not simply use a for loop and increment the index counter with a value of 2?
for ($i = 0; $i -lt $a.Count; $i += 2) {
Write-Host $a[$i].Name
}

Related

ForEach loop is leaving my variable empty

Complete novice to Powershell so, my apologies ahead of time for the likely easy task for most in this group.
$File = Get-ChildItem "C:\location"
$ACLs= foreach ($item in $File) {get-acl $item.FullName}
$foldernames = $acls.pschildname
$ACLNames = $acls.Access.IdentityReference
$ACLNamesNew = foreach ($ACLName in $ACLNames) {
$ACLNameString = $ACLname.Value.ToString()
$ACLNameFormatted = $ACLNameString.Split("\")[0]
}
Ultimate goal is to take the string, trim just the domain/group names from the string, pull only the unique values, and write whats left to the variable $ACLNames. Haven't even tried filtering unique as even the above leaves $ACLNamesNew empty. The foreach seams sound from my testing, but it doesn't write the values into the variable as I expected.
If you want to collect the result of a loop you have to output something inside your loop. This should be enough .. if I've got you right
$fileList = Get-ChildItem 'C:\location'
$ACLList = foreach ($file in $fileList) { get-acl $file.FullName }
$ACLNameList = $ACLList.Access.IdentityReference
$ACLNameNewList = foreach ($ACLName in $ACLNameList) {
$ACLname.Value.ToString().Split("\")[0]
}
$ACLNameNewList

PowerShell, more efficient way to find duplicate folders

I wrote a little function to scan each folder in $PSModulePath to see if duplicate folder names exist in the various paths in there (as I've found this problem happening quite often in my PowerShell environments!). I use simple logic and I was wondering if some PowerShell gurus maybe have more compact / faster / more efficient ways to achieve a sweep like this (I quite often find that those better at PowerShell seem to have 2-line solutions to something that takes me 15 lines! :-) )?
I'm just taking a path in $PSModulePath and creating an array of the subfolder names there, then looking at the subfolders of the other paths in $PSModulePath and comparing them one by one against the array that I made for the first path, and then repeating for the other paths.
function Find-ModuleDuplicates {
$hits = ""
$ModPaths = $env:PSModulePath -Split ";" -replace "\\+$", "" | sort
foreach ($i in $ModPaths) {
foreach ($j in $ModPaths) {
if ($j -notlike "*$i*") {
$arr_i = (gci $i -Dir).Name
$arr_j = (gci $j -Dir).Name
foreach ($x in $arr_j) {
if ($arr_i -contains $x) {
$hits += "Module '$x' in '$i' has a duplicate`n"
}
}
}
}
}
if ($hits -ne "") { echo "" ; echo $hits }
else { "`nNo duplicate Module folders were found`n" }
}
The following is a solution using Group-Object.
$env:PSModulePath.Split(";") | gci -Directory | group Name |
where Count -gt 1 | select Count,Name,#{ n = "ModulePath"; e = { $_.Group.Parent.FullName } }

Script to enforce backup retention policy

I have a need for a script that can look at our backup folders (we take full backups of machines) and ensure that we never store more than the two most recent backups for each computer. I am currently working on a powershell loop that should compare each file name to every other file in the backup folder. Each machine has multiple backup files that can be identified by a matching prefix and a unique ID number separated by a delimiting character.
prefix = machine name and unique ID denotes each backup.
I am working on setting up the outer loop and running into a wall. I am storing the list of files to be checked in $array.
I have a nested for loop that I want to have check each file against every other file in the array. I have a query that should rebuild the array and exclude the current reference file each iteration.
for ($i=0; $i -lt 5; $i++) {
$array = Get-ChildItem -Path C:\Users\Aaron.Trujillo\Desktop\test
$array = Get-ChildItem -Path C:\Users\Aaron.Trujillo\Desktop\test -Exclude $array[$i]
for ($j=0; $j -lt 5; $j++){
Write-Host "i: $i j: $j"
Write-Host "Array i: $($array[$i].name) Array j: $($array[$j].name)"
if($array[$i].name -ne $array[$j].name)
{
Write-Host "truthval=false"
$truthval="false"
}
else
{
Write-Host "truthval=true"
$truthval = "true"
}
}
}
I was expecting this loop to compare the first five files in the array against each other (excluding the current reference file) and change the variable for each file, but for some reason the reference file isn't getting excluded.
Something must be wrong with the logic, but I'm struggling to spot it.
I see several flaws with your code.
As $array is presumably populated by a Get-ChildItem (which on ntfs formatted drives returns a sorted output) the nested loops aren't neccessary at all.
When iterating with one for simply compare with the predessor/follower index +/- 1.
As Names include per your description an ID, they are always different. So you've to split the name at the nebulous delimiter and check if the machine name is the same.
your if command doesn't emit anything, it just overwrites the same variable on every iteration.
With #Lee_Dailey's good idea of grouping you can split the names on the fly and check the count per group, sort files per group by LastWriteTime and only keep the newest 2.
So given a hypothetical tree of files
> tree /F
A:.
└───Backup
Alpha_8.bak
Bravo_2.bak
Bravo_5.bak
Bravo_6.bak
Bravo_7.bak
Bravo_9.bak
Charlie_1.bak
Charlie_10.bak
Charlie_3.bak
Charlie_4.bak
this small script:
$BackupDir = 'A:\Backup'
$Delim = '_'
Get-ChildItem -Path $BackupDir -File |
Group-Object {($_.BaseName -split $Delim)[0]}
yields:
Count Name Group
----- ---- -----
1 Alpha {Alpha_8.bak}
5 Bravo {Bravo_2.bak, Bravo_5.bak, Bravo_6.bak, Bravo_7.bak...}
4 Charlie {Charlie_1.bak, Charlie_10.bak, Charlie_3.bak, Charlie_4.bak}
You can then iterate the groups and delete files per group by whatever means,
for example delete all but the 2 newest (defined per LastWriteTime) files per machine:
Get-ChildItem -Path $BackupDir -File |
Group-Object {($_.BaseName -split $Delim)[0]} | ForEach-Object {
$_.Group | Sort LastWriteTime -Desc | Select -skip 2 | Remove-Item -WhatIf
}
If the output looks OK, remove the trailing -WhatIf
I think it's the brackets around the $array[$j.name] i.e. it should be:
if($array[$i].name -ne $array[$j].name)
Edit:
For troubleshooting try manually outputting your information:
for ($i=0; $i -lt 5; $i++) {
for ($j=$i+1; $j -lt 5; $j++){
Write-Host "i: $i j: $j"
Write-Host "Array i: $($array[$i].name) Array j: $($array[$j].name)"
if($array[$i].name -ne $array[$j].name)
{
Write-Host "truthval=false"
$truthval="false"
}
else
{
Write-Host "truthval=true"
$truthval = "true"
}
}
}

While loop does not produce pipeline output

It appears that a While loop does not produce an output that can continue in the pipeline. I need to process a large (many GiB) file. In this trivial example, I want to extract the second field, sort on it, then get only the unique values. What am I not understanding about the While loop and pushing things through the pipeline?
In the *NIX world this would be a simple:
cut -d "," -f 2 rf.txt | sort | uniq
In PowerShell this would be not quite as simple.
The source data.
PS C:\src\powershell> Get-Content .\rf.txt
these,1,there
lines,3,paragraphs
are,2,were
The script.
PS C:\src\powershell> Get-Content .\rf.ps1
$sr = New-Object System.IO.StreamReader("$(Get-Location)\rf.txt")
while ($line = $sr.ReadLine()) {
Write-Verbose $line
$v = $line.split(',')[1]
Write-Output $v
} | sort
$sr.Close()
The output.
PS C:\src\powershell> .\rf.ps1
At C:\src\powershell\rf.ps1:7 char:3
+ } | sort
+ ~
An empty pipe element is not allowed.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : EmptyPipeElement
Making it a bit more complicated than it needs to be. You have a CSV without headers. The following should work:
Import-Csv .\rf.txt -Header f1,f2,f3 | Select-Object -ExpandProperty f2 -Unique | Sort-Object
Nasir's workaround looks like the way to go here.
If you want to know what was going wrong in your code, the answer is that while loops (and do/while/until loops) don't consistently return values to the pipeline the way that other statements in PowerShell do (actually that is true, and I'll keep the examples of that, but scroll down for the real reason it wasn't working for you).
ForEach-Object -- a cmdlet, not a built-in language feature/statement; does return objects to the pipeline.
1..3 | % { $_ }
foreach -- statement; does return.
foreach ($i in 1..3) { $i }
if/else -- statement; does return.
if ($true) { 1..3 }
for -- statement; does return.
for ( $i = 0 ; $i -le 3 ; $i++ ) { $i }
switch -- statement; does return.
switch (2)
{
1 { 'one' }
2 { 'two' }
3 { 'three' }
}
But for some reason, these other loops seem to act unpredictably.
Loops forever, returns $i (0 ; no incrementing going on).
$i = 0; while ($i -le 3) { $i }
Returns nothing, but $i does get incremented:
$i = 0; while ($i -le 3) { $i++ }
If you wrap the expression inside in parentheses, it seems it does get returned:
$i = 0; while ($i -le 3) { ($i++) }
But as it turns out (I'm learning a bit as I go here), while's strange return semantics have nothing to do with your error; you just can't pipe statements into functions/cmdlets, regardless of their return value.
foreach ($i in 1..3) { $i } | measure
will give you the same error.
You can "get around" this by making the entire statement a sub-expression with $():
$( foreach ($i in 1..3) { $i } ) | measure
That would work for you in this case. Or in your while loop instead of using Write-Output, you could just add your item to an array and then sort it after:
$arr = #()
while ($line = $sr.ReadLine()) {
Write-Verbose $line
$v = $line.split(',')[1]
$arr += $v
}
$arr | sort
I know you're dealing with a large file here, so maybe you're thinking that by piping to sort line by line you'll be avoiding a large memory footprint. In many cases piping does work that way in PowerShell, but the thing about sorting is that you need the whole set to sort it, so the Sort-Object cmdlet will be "collecting" each item you pass to it anyway and then do the actual sorting in the end; I'm not sure you can avoid that at all. Admittedly letting Sort-Object do that instead of building the array yourself might be more efficient depending on how its implemented, but I don't think you'll be saving much on RAM.
other solution
Get-Content -Path C:\temp\rf.txt | select #{Name="Mycolumn";Expression={($_ -split "," )[1]}} | select Mycolumn -Unique | sort

Conditional Multiplication in Loop

I've got a script that goes through a CSV file with two formats of data (XY:ZABC or 0.xyz). The values are then saved in a CSV file with one column and variable number of rows. I am trying to setup my script such that, for numbers of value 0.xyz, it will multiply by 1440 and then store it in $Values. The numbers of format XY:ZABC will be stored as they are in $Values as well.
$Values = #(Get-Content *\source.csv -Raw) -split '\s+' |
Where-Object {$_ -like '*:*' -or '0.*'}
"UniqueActiveFaults" | Out-File *\IdealOutput.csv
$Values | Sort-Object -Unique | Out-File *\IdealOutput.csv
I've tried to do this by adding the following code:
foreach ($i in $Values) {
if ($i -lt 1) {$i*1440}
}
I've also tried to do it with a do {$i*1440} while ($I -lt 1) loop, but the result is the number 0.xyz shown 1440 times. I believe it's due to the type of data that $Values is taking, but not sure.
Sample data:
0.12345
00:9090 90:4582
0.12346
0.1145
0.145654
0.5648
01:9045 90:4500
90:4546
BA: 1117 BA:2525
In your code, $Values is an array of strings. The "multiply" operation on a string is to repeat it. To treat it like a number, cast to float before multiplying.
foreach ($i in $Values) {
if ($i -lt 1) {[float]$i * 1440}
}
As Tony Hinkle pointed out, this loop will simply output the result of the operation to the caller (or the console if you don't pipe it). If you want to your array to reflect the change, you have to store it back.
for ($i = 0; $i -lt $Values.length; $i++) {
if ($Values[$i] -lt 1) { [float]$Values[$i] *= 1440 }
}
Be aware this will leave some of your values array as strings and some as floats. Depending on what you do with it, you might have to do further casts.
When you use $i*1440 that is simply telling Powershell to multiply the two values and return the product. If you want to change the value of $i, you need to use $i = $1 * 1440.
You may have other issues as well, but this is assuming that you are getting the correct values assigned to $i from the input.