PowerShell greater than also includes item that is equal - powershell

I am writing a PowerShell script to sort the directory and return folders with the names that start with the number sequence that is greater than specified sequence, but -gt operator acts as a -ge operator.
Here is the code I'm running:
Get-ChildItem C:\Users\USER\Testing -recurse |
Where-Object { $_.PSIsContainer -ge $true -and $_.Name -gt "003" -and $_.Name -match '^\d+.*$' } |
Select-Object Name
The response I get is:
005-folder
003-folder
004-folder
There seems to be the similar but opposite pattern for -le and -lt operators. Both of them do NOT include the equal item. So when I run
Get-ChildItem C:\Users\USER\Testing -recurse |
Where-Object {$_.PSIsContainer -ge $true -and $_.Name -le "003" -and $_.Name -match '^\d+.*$' } |
Select-Object Name
The response I get is:
001-folder
002-folder
I can't seem to find anything on the internet that solves the issue I am having, so I assume something in my scripts breaks the -gt and -le operators?

I think I figured out the issue few minutes after I posted it. '003-folder' will be greater than '003' cause I'm comparing strings. Need to truncate first.
Get-ChildItem C:\Users\USER\Testing -recurse -Directory |
Where-Object {$_.Name.subString(0, 3) -gt '003' -and $_.Name -match '^\d+.*$' } |
Select-Object Name

Related

Is there a better way to write this to combine files

I am new to PowerShell, but i am slowly getting the hang of it.
I was wondering if there is a better way to write this?
In a single directory I have monthly reports for 14 names as text files. The below looks at the directory, searches for the NAME1 and for any files containing Jan, Feb, Mar and combines them into a single file and exports the combined file to another location with a specific name:
Get-childitem -Path 'C:\Powershell\Attempt\*.txt' | where-object {$_.name -like "*NAME1**JAN*" -or $_.name -like "*NAME1**FEB*" -or $_.name -like "*NAME1**MAR*"} | get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q1_.txt -Encoding ascii
Get-childitem -Path 'C:\Powershell\Attempt\*.txt' | where-object {$_.name -like "*NAME1**APR*" -or $_.name -like "*NAME1**MAY*" -or $_.name -like "*NAME1**JUN*"} | get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q2_.txt -Encoding ascii
Get-childitem -Path 'C:\Powershell\Attempt\*.txt' | where-object {$_.name -like "*NAME1**JUL*" -or $_.name -like "*NAME1**AUG*" -or $_.name -like "*NAME1**SEP*"} | get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q3_.txt -Encoding ascii
Get-childitem -Path 'C:\Powershell\Attempt\*.txt' | where-object {$_.name -like "*NAME1**OCT*" -or $_.name -like "*NAME1**NOV*" -or $_.name -like "*NAME1**DEC*"} | get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q4_.txt -Encoding ascii
Is this possible with a loop? or is it better to just write out all 14 names and quarterly combinations?
The above also creates blank Qx.txt files if the source files don't exist (does that make any sense) so I have also written this to remove those that are blank
get-childitem C:\powershell\attempt\test -Recurse | foreach {
if($_.Length -eq 0){
Write-Output "Removing Empty File $($_.FullName)"
$_.FullName | Remove-Item -Force
}
}
if( $_.psiscontainer -eq $true){
if((gci $_.FullName) -eq $null){
Write-Output "Removing Empty folder $($_.FullName)"
$_.FullName | Remove-Item -Force
}
}
Is there a way to incorporate this into the main script, or is it better to keep this as i "tidy up" at the end?
I do have another query, but I'm not sure if it's better being a separate post (I don't want to put too much in this one if it's not the way) It is about how to rename the files from different variables. I can get the different name variables, but not working harmoniously within the above script - this comes down to my lack of knowledge
Many thanks in advance,
Kind Regards
Before trying to create a loop, let's do something about those clunky -like clauses. We can make them go away with a single -match clause.
The expression
$_.name -like "*NAME1**JAN*" -or $_.name -like "*NAME1**FEB*" -or $_.name -like "*NAME1**MAR*"
is equivalent to
$_.name -match "NAME1.*(JAN|FEB|MAR)"
The -like operator uses Wildcards. Wildcards are nice, but the -match operator works with full fledged Regular Expressions which are much more versatile.
These are not compatible with each other - some Wildcard expressions are valid Regular Expressions and vice versa, but match different strings.
Don't forget that -match and -like are not case-sensitive. For case-sensitive comparisons use -cmatch and -clike
Now we can solve the rest of your problem using a pair of loops and arrays.
You'll need to create a $names array by typing out all 14 names.
$names = #("NAME1", "NAME2", "NAME3", ...)
Luckily, your example names have a nice pattern, so we can use that
$ctr = 0
$names = #("NAME") * 14 | ForEach-Object {$_ + ++$ctr}
We'll need another array containing the months in our Regular Expressions
$quarts = #("JAN|FEB|MAR", "APR|MAY|JUN", "JUL|AUG|SEP", "OCT|NOV|DEC")
And now we shall loop
$ctr = 0
$names = #("NAME") * 14 | Foreach-Object {$_ + ++$ctr}
$quarts = #("JAN|FEB|MAR", "APR|MAY|JUN", "JUL|AUG|SEP", "OCT|NOV|DEC")
$container = "C:\Powershell\Attempt"
$files = Get-ChildItem -Path $container -Filter *.txt
foreach ($name in $names)
{
$quart_num = 1
foreach ($quart in $quarts)
{
$files |
Where-Object {$_.name -match "${name}\D.*(${quart})"} |
Get-Content |
Out-File "${container}\test\${name}_Q${quart_num}.txt" -Encoding ascii
$quart_num += 1
}
}
# Remove any empty files
Get-ChildItem -Path "${container}\test" | Where-Object {$_.Length -eq 0} | Remove-Item
Note that the script will error out if the path ${container}\test doesn't exist.
Note that I've slightly changed the Regular expression in the script used - the regular expressions look like NAME1\D.*(JAN|FEB|MAR) instead of NAME1.*(JAN|FEB|MAR). This is so that a file named NAME14_JAN.txt doesn't match the regular expression corresponding to NAME1 as well as NAME14
The following solution uses the Group-Object cmdlet; it may not be the easiest to understand, but it is concise and doesn't require looping over the input files multiple times:
$sourceDir = 'C:\Powershell\Attempt'
$outDir = 'C:\Powershell\Attempt\test' # Make sure this dir. exists.
$months = 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
# The regex to match file names against.
$regex = '\b(NAME\d+).*[^a-z]({0})[^a-z]' -f ($months -join '|')
Get-ChildItem -File -Path "$sourceDir\*.txt" |
Group-Object { # Group files by shared name and quarter
if ($_.Name -match $regex) {
$qIndex = 1 + [math]::Floor([Array]::IndexOf($months, $Matches[2].ToUpper()) / 3)
'{0}\{1}_Q{2}.txt' -f $outDir, $Matches[1], $qIndex # full output path
} else {
'N/A'
}
} |
Where-Object Name -ne 'N/A' | # Weed out non-matching files
ForEach-Object {
Set-Content -Encoding Ascii -LiteralPath $_.Name -Value ($_.Group | Get-Content)
}
If there are only a few files, what you have looks pretty good to me. But if there are a large amount of files or the files are large, you have a couple places where you can gain some performance.
First is by using the -Filter parameter, like this:
Get-childitem -Path 'C:\Powershell\Attempt\*' -Filter '*NAME1*.txt'
[NOTE: The -Filter parameter usually only works well on one filter, so you'll still want to use the where-object {$_.name -like... for the different months]
The second place you can gain some performance is by setting the 'C:\Powershell\Attempt\*' -Filter '*NAME1*.txt' command equal to a variable. Setting the results to a variable allows you to make the search once and then reuse the results:
$name1 = Get-childitem -Path 'C:\Powershell\Attempt\*' -Filter '*NAME1*.txt'
You can put this into a loop if desired but in my opinion it isn't worth the effort. You can however get rid of the blank text files with some sort of existence check. Here is how the entire thing could look:
$name1 = Get-childitem -Path 'C:\Powershell\Attempt\*' -Filter '*NAME1*.txt'
if (1 -eq ($name1 | where-object {$_.name -like "*JAN*" -or $_.name -like "*FEB*" -or $_.name -like "*MAR*"}).Count){
get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q1_.txt -Encoding ascii}
if (1 -eq ($name1 | where-object {$_.name -like "*APR*" -or $_.name -like "*MAY*" -or $_.name -like "*JUN*"}).Count){
get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q2_.txt -Encoding ascii}
if (1 -eq ($name1 | where-object {$_.name -like "*JUL*" -or $_.name -like "*AUG*" -or $_.name -like "*SEP*"}).Count){
get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q3_.txt -Encoding ascii}
if (1 -eq ($name1 | where-object {$_.name -like "*OCT*" -or $_.name -like "*NOV*" -or $_.name -like "*DEC*"}).Count){
get-content | Out-File C:\Powershell\Attempt\test\NAME1_Q4_.txt -Encoding ascii}

Get-ChildItem Where-Object -notlike $array - Is there a way to do this?

I have written a script that will recurse a specified folder and do some analysis on the files within it. I need to exclude specified sub-folders in the analysis. This list of exclusions changes dependent on the base folder being analysed. I have the script working using a long pattern like this:
Get-ChildItem -File -Recurse $source_folder |
Where-Object {
$_.FullName -notlike "*\folder_name0\*" -and
$_.FullName -notlike "*\folder_name1\*" -and
$_.FullName -notlike "*\folder_name2\*" -and
$_.FullName -notlike "*\folder_name3\*" -and
$_.FullName -notlike "*\folder_name4\*"
}
but this is not very reusable. I would like to be able to store exception lists in .CSVs and call the exception list I need based on the folder set I am analyzing. What I would like to do is something like:
$exception_list = Import-CSV .\exception_list
Get-ChildItem -File -Recurse $source_folder |
Where-Object {$_.FullName -notlike $exception_list}
but this does not work. I suspect because I can't specify and 'and' or an 'or' between the elements in the array. I did briefly consider trying to create the whole argument on the fly using a foreach($exception in $exception_list){$argument += "$_.FullName -notlike $exception -and"}, but that got silly and complex pretty quickly since you still have to remove the last 'and'.
Is there an efficient way to do this?
this builds an array of partial names to be excluded, and uses that array to build a regex OR for use in a -notmatch test.
$ExcludedDirList = #(
'PSES-'
'vscode'
'Test_'
)
# regex uses the pipe symbol as the logical "OR"
$RegexExcludedDirList = $ExcludedDirList -join '|'
$Results = Get-ChildItem -Path $env:TEMP -File -Recurse |
Where-Object {
$_.DirectoryName -notmatch $RegexExcludedDirList
}
I really like #lee_dailey's pattern of creating the regex. An alternative method could be to use -in or -notin to compare collections.
Using Pester:
It 'Filters correctly' {
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$list | Where-Object { $_ -notin $filter} | should -be $expected
}
Or just plain comparison operators:
$list = #('fileA', 'file1', 'file2', 'file32')
$filter = #('file1', 'file3')
$expected = #('fileA', 'file2', 'file32')
$newlist = $list | Where-Object { $_ -notin $filter}
(Compare-Object $newlist $expected).length -eq 0
> True

How exploit Get-ChildItem result?

I would like list all path to go to folder name ending with "_S" recursively.
I did that:
Get-ChildItem -Recurse | Where-Object {($_.Attributes -match "Directory") -and ($_.Name.EndsWith("_S") -and ($_.PSIsContainer -eq 1))}
But the result isn't an array. How i can to exploit the results ?
My goal is to have something like that:
Myfolder\folder1\folder1_S
Myfolder\folder2_S
Use Select-Object and grab the FullName of the file.
Also, as stated in the comments on the question by #Paul ($_.Attributes -match "Directory") and ($_.PSIsContainer -eq 1) is redundant, might want to remove one of them.
Get-ChildItem -Recurse | Where-Object {($_.Attributes -match "Directory") -and ($_.Name.EndsWith("_S"))} | Select-Object -ExpandProperty FullName
The above can also be refactored in PowerShell 3.0+, to
Get-ChildItem -Recurse -Directory -Filter *_S | Select-Object -ExpandProperty FullName
which would recursively get the path of all directories ending with "_S"

Count directories with a certain name that also contain file

I am currently working on a script that will count all of the directories (within a specified path, \\servername\logs\failures) which have a naming convention of "P0*" or "MININT*" - but the folders named as "MININT*" also must have a subfolder that contains a specified log file. Also, it should only be counting folders created in the last 24 hours.
Here is my current code, it keeps returning a zero value and I am not sure why. I have been searching for hours and tried different methods using recurse, Test-Path, etc. to no avail.
$imagefailures = Get-ChildItem '\\servername\logs\failures' -Directory |
Where-Object {
($_.Name -like "P0*") -or
(($_.Name -like "MININT*") -and (Test-Path "\WinPE_TS_X_Win\SMSTSLog\Get-Name.log")) -and
($_.LastWriteTime -gt (Get-Date).AddHours(-24))
} | Measure-Object | select -ExpandProperty Count
Try this:
$imagefailures = Get-ChildItem '\\servername\logs\failures' -Directory |
Where-Object {
($_.Name -like "P0*") -or
(($_.Name -like "MININT*") -and (Test-Path "$($_.FullName)\WinPE_TS_X_Win\SMSTSLog\Get-Name.log")) -and
($_.LastWriteTime -gt (Get-Date).AddHours(-24))`
} | Measure-Object | select -ExpandProperty Count
The path you are testing will always just be "\WinPE_TS_X_Win\SMSTSLog\Get-Name.log" if you do not append it to the folder path you are iterating on.

Prioritizing "$_.Name" over "$_.LastAccessTime"

I created a script that allows me to search for and ignore directories from a Remove-Item statement, and the script works, but not necessarily to the extent I need it to.
Get-ChildItem -Path $Path |
Where-Object {
($_.LastAccessTime -lt $Limit) -and
-not ($_.PSIsContainer -eq $True -and $_.Name -contains ("2013","2014","2015"))
} | Remove-Item -Force -Recurse -WhatIf
This script is currently finding and deleting all objects that
Have not been accessed in the given time period
But what I need this script to do is find and delete all objects that
Have not been accessed in the given time period AND
Exclude directories that contain the name of "2013", "2014", or "2015".
I'm not arguing that the script "isn't working properly", but the thesis of my question is this:
How do I program this script to look at the directory name first, and then the last access date? I don't know where and how to tell this script that the $_.Name needs to take precedence over the $_.LastAccessTime -lt $Limit.
Currently the logic of your condition is this:
Delete objects that were last accessed before $Limit and are not folders whose name contains the array ["2013","2014","2015"].
The second condition is never true, because a string can never contain an array of strings.
Also, the last modification time is stored in the LastWriteTime property.
What you actually want is something like this:
Where-Object {
$_.LastWriteTime -lt $Limit -and
-not ($_.PSIsContainer -and $_.Name -match '2013|2014|2015')
}
If the directory names consist only of the year and nothing else you could also use this:
Where-Object {
$_.LastWriteTime -lt $Limit -and
-not ($_.PSIsContainer -and '2013','2014','2015' -contains $_.Name)
}
Note the reversed order of the last clause (array -contains value).