export folders list in specific pattern PowerShell - list

I need to export a list of folders in a specific pattern
my code is
$list = Get-ChildItem -Path c:\test\ -Directory | Select-Object BaseName
I need the final output to be.
<object> folder name here </object>
so I can use it later to update an xml file I have
how can i do it?

$list = Get-ChildItem -Path d:\tmp\ -Directory | Select-Object #{name="baseNameInTag"; expression = {"<object>{0}</object>" -f $_.BaseName}}
Select-Object

Use member-access enumeration to get all .BaseName values from the output objects emitted by Get-ChildItem.
As an aside: For directories, .BaseName is - perhaps surprisingly - the same as .Name - only for files is an extension, if present, removed.
Use ForEach-Object to embed each base name in (the string representation of) an XML element using an expandable (double-quoted) string ("..."):
(Get-ChildItem -Directory c:\test).BaseName |
ForEach-Object { "<object>$_</object>" }

Related

Renaming multiple files in directory using a specific string in each file

I have a folder with multiple files and need to rename them to a string inside of the folder. The string is the date of interaction.
Currently the files are named as
AUDIT-1.log
AUDIT-2.log
AUDIT-3.log
ect..
I need to have them as
AUDIT-11-08-22-1.log
AUDIT-11-07-22-2.log
AUDIT-11-08-22-3.log
The issue I am having with the current iteration of the code, the dates of all files are collected and it attempts to rename the file with all dates
EXAMPLE:
NewName: 11-08-22 11-07-22 11-06-22 11-09-22 11-08-22 11-07-22 11-06-22 11-09-22-1.LOG
OldName: C:\TestTemp\AUDIT-2.LOG
There is only one date in each file.
The following is my current code:
$dir ="C:\TestTemp"
$files = Get-ChildItem -Path "$dir\*.log"
$RegexDate = '\d\d\/\d\d\/\d\d'
Measure-Command{
$file_map = #()
foreach ($file in $files) {
$DateName= Get-Content $files |
Select-String $RegexDate |
foreach-object { $_.Matches.Value } |
Select-Object
$NewDateName= $DateName.replace('/','-')
$b = 1
$file_map += #{
OldName = $file.Fullname
NewName = "$NewDateName-$b.LOG" -f $(Get-Content $file.Fullname | Select-Object $NewDateName.Fullname)
}
}
$file_map | ForEach-Object { Rename-Item -Path $_.OldName -NewName $_.NewName }
}
As pointed out in the comments by Santiago Squarzon, the immediate fix is to swap $files, for $file. For code brevity, here's a single pipeline solution you can implement to attain the same results:
Select-String -Path "$dir\*.log" -Pattern '(\d+\/){2}\d+' |
Rename-Item -NewName {
$_.FileName -replace '-', "-$($_.Matches.Value.Replace('/','-'))-"
} -WhatIf
Again, as mentioned in the comments, the use of Select-String allows the reading of file(s) presenting the opportunity to pipe directly into Rename-Item via parameter binding through its Path property. So, using a scriptblock for the new name replacement we're essentially inserting the value found from it's pattern matched into the file name where - would have been.
The -WhatIf safety/common parameter can be removed when you've dictated those are the results you are after.
This will rename the files using their last write time.
If the files were already in that format, they will not be renamed.
There is a hashtable to track the increment of the suffix for the date of the file. This way the files can be organized by date.
$dir = "C:\TestTemp"
$files = Get-ChildItem -Path "$dir\*.log"
#Hashtable to track the suffix for the files
[hashtable]$dateTracking = #{}
#Using padding to format the suffix with two digits, in case there more then 9 files
#incrase it if you have more then 99 files per day increase padding
$suffixPadding = '{0:d2}'
foreach ($file in $files) {
#Don't rename files that were already renamed
if ($file.Name -notmatch "AUDIT-\d{2}-\d{2}-\d{2}-\d{2}\.log") {
$date = $file.LastWriteTime.ToString("MM-yy-dd")
#If the date is not entered in the hashtable add it with suffix 01
if (-not $dateTracking.ContainsKey($date)) {
$dateTracking.Add($date, $suffixPadding -f 1)
}
#Else increment suffix
else {
$dateTracking[$date] = $suffixPadding -f ([int]$dateTracking[$date] + 1)
}
#Here we use the date in the name of the file and getting the suffix from the hashtable
Write-Host "Renaming $($file.Name) to AUDIT-$date-$($dateTracking[$date]).log"
Rename-Item -Path $file -NewName "AUDIT-$date-$($dateTracking[$date]).log"
}
}

Using "notin" with matching groups

Using powershell, I am trying to determine which perl scripts in a directory are not called from any other script. In my Select-String I am grouping the matches because there is some other logic I use to filter out results where the line is commented, and a bunch of other scenarios I want to exclude(for simplicity I excluded that from the code posted below). My main problem is in the "-notin" part.
I can get this to work if I remove the grouping from Select-string and only match the filename itself. So this works.
$searchlocation = "C:\Temp\"
$allresults = Select-String -Path "$searchlocation*.pl" -Pattern '\w+\.pl'
$allperlfiles = Get-Childitem -Path "$searchlocation*.pl"
$allperlfiles | foreach-object -process{
$_ | where {$_.name -notin $allresults.matches.value} | Select -expandproperty name | Write-Host
}
However I cannot get the following to work. The only difference between this and above is the value for the "-Pattern" and the value after "-notin". I'm not sure how to use "notin" along with matching groups.
$searchlocation = "C:\Temp\"
$allresults = Select-String -Path "$searchlocation*.pl" -Pattern '(.*?)(\w+\.pl)'
$allperlfiles = Get-Childitem -Path "$searchlocation*.pl"
$allperlfiles | foreach-object -process{
$_ | where {$_.name -notin $allresults.matches.groups[2].value} | Select -expandproperty name | Write-Host}
At a high level the code should search all perl scripts in a directory for any lines that execute any other perl script. With that I now have $allresults which basically gives me a list of all perl scripts called from other files. To get the inverse of that(files that are NOT called from any other file) I get a list of all perl scripts in the directory, cycle through those and list out the ones that DONT show up in $allresults.
When you select a grouping you need to do so using a Select statement, or iteratively in a loop, otherwise you are only going to select the value from the Nth match.
IE if your $Allresults object contains
File.pl, File 2.pl, File 3.pl
Then $allresults.Matches.Groups[2].value Only Returns File2.pl
Instead, you need to select those values!
$allresults | select #{N="Match";E={ $($_.Matches.Groups[2].value) } }
Which will return:
Match
-----
File1.pl
File2.pl
File3.pl
In your specific example, each match has three sub-items, the results will be completely sequential, so what you would term "match 1, group 1" is groups[0] while "match 2, group 1" is groups[3]
This means the matches you care about (those with grouping 2) are in the array values contained in the set {2,5,8,11,...,etc.} or can be described as (N*3-1) Where N is the number of the match. So For Match 1 = (1*3)-1 = [2]; while For Match 13 = (13*3)-1 = [38]
You can iterate through them using a loop to check:
for($i=0; $i -le ($allresults.Matches.groups.count-1); $i++){
"Group[$i] = ""$($allresults.Matches.Groups[$i].value)"""
}
I noticed that you took the time to avoid loops in collecting your data, but then accidentally seem to have fallen prey to using one in matching your data.
Not-In and other compares when used by the select and where clauses don't need a loop structure and are faster if not looped, so you can forego the Foreach-object loop and have a better process just by using a simple Where (?).
$SearchLocation = "C:\Temp\"
$FileGlob = "*.pl"
$allresults = Select-String -Path "$SearchLocation$FileGlob" -Pattern '(.*?)([\w\.]+\.bat)'
$allperlfiles = Get-Childitem -Path "$SearchLocation$FileGlob"
$allperlfiles | ? {
$_.name -notin $(
$allresults | select #{N="Match";E={ $($_.Matches.Groups[2].value) } }
)
} | Select -expandproperty name | Write-Host
Now, that should be faster and simpler code to maintain, but, as you may have noticed, it still has some redundancies now that you are not looping.
As you are piping it all into a Select which can do the work of the where, and what's more you only are looking to match the NAME property here so you can either for-go the last select by only piping the name of the file in the first place, or you can forgo the where and select exactly what you want.
I think the former is far simpler, and the latter is useful if you are going to actually do something with those other values inside the loop that we don't know yet.
Finally, Write-host is likely redundant as any object output will echo to the console.
Here is that version which incorporates the removal of the unnecessary loops and removes redundancies related to the output of the info you wanted, all together.
$SearchLocation = "C:\Temp\"
$FileGlob = "*.pl"
$allresults = Select-String -Path "$SearchLocation$FileGlob" -Pattern ('(.*?)([\w\.]+\'+$FileGlob+')')
$allperlfiles = Get-Childitem -Path "$SearchLocation$FileGlob"
$allperlfiles.name | ? {
$_ -notin $(
$allresults | select #{
N="Match";E={
$($_.Matches.Groups[2].value)
}
}
)
}

Search for RegEx string in files and return ONLY file name, path and string

i am kinda stuck with this search for Regex string. The scenario is as follows:
I have a bunch of files of certain extension (*.tlt) with random content
All the files are across the subfolders of BETA folder on drive F:
Each one of the files has at least one Revision 1.234 somewhere in the content. (sometimes multiple times - only the first appearance is important)
This is what I have so far:
$files = gci f:\beta\ -Include "*.tlt" -Recurse
$results = $files |
Select-String -Pattern 'Revision:.+.{1}[.]\d{1,3}'|
ForEach-Object { $_.Matches } |
select Value |
Format-Table -GroupBy Filename
What I need is a PowerShell script that searches through the files and returns the list of files with the full path and ONLY the Revision 1.234 but not the whole line.
A single-pipeline solution is possible with the help of calculated properties:
Get-ChildItem f:\beta -Filter *.tlt -Recurse |
Select-String -List -Pattern 'Revision:.+?\.\d{3}' |
Select-Object #{ n='FullName'; e='Path' }, #{ n='Revision'; e={ $_.Matches.Value } }
Sample output:
FullName Revision
-------- --------
/Users/jdoe/foo.tlt Revision: 1.234
/Users/jdoe/sub/bar.tlt Revision: 10.235
As mentioned in TheIncorrigible1's answer, using -Filter performs much better than using -Include, because -Filter filters at the source (lets the filesystem provider do the filtering) rather than collecting all file-info objects first and then letting PowerShell do the filtering.
Select-String -List limits matching in each input file to the first match.
Each match output by Select-String is a [Microsoft.PowerShell.Commands.MatchInfo] instance, which contains rich metadata about each match, such as .Path with the full input filename, and .Matches with information about what the regex (-Pattern) matched - this metadata is used to populate the output custom objects created by Select-Object via the aforementioned calculated properties.
You were close, but you inevitably need to loop through your files. Note -Filter is significantly faster than -Include since it doesn't collect every object before filtering.
$fileList = Get-ChildItem -Path F:\beta -Filter *.tlt -Recurse
$results = foreach ($file in $fileList)
{
$find = $file | Select-String -Pattern '(Revision:.+?\.\d{1,3})'
if ($find)
{
#{
Path = $file.FullName
Rev = $find.Matches.Groups[0].Value
}
}
}

Trying to create a power shell script that removes text in a filename between two brackets using regex

I am trying to write a script to take a file name and remove any pair of brackets and the text between them from the string
get-childItem *.* -recurse |
foreach-object {$_ -replace '\(([^\)]+)\)', ''}
this will output a list of new values for every file in the folder to the prompt as it should look, however what I can't seem to find is a way to set the new values as the filenames, the plan is to do this for multiple files in a folder with the format "name(Randomnumbers).ext"
Any help is appreciated
From my understanding of your question, you want to rename each with the names contained within the parenthesis. To accomplish that, you can use the $Matches variable that is written by the -match operator. I'm also assuming you want to maintain the file extension.
Get-ChildItem -Recurse | ForEach-Object {
if ($_ -match '(?<name>.*)(?:\([^\)]+\))(?<ext>.*)') {
Rename-Item $_ "$($matches['name'])$($matches['ext'])"
}
}

Powershell 'where' statement -notcontains

I have a simple excerpt form a larger script, basically I'm trying to do a recursive file search, including sub-directories (and any child of the exclude).
clear
$Exclude = "T:\temp\Archive\cst"
$list = Get-ChildItem -Path T:\temp\Archive -Recurse -Directory
$list | where {$_.fullname -notlike $Exclude} | ForEach-Object {
Write-Host "--------------------------------------"
$_.fullname
Write-Host "--------------------------------------"
$files = Get-ChildItem -Path $_.fullname -File
$files.count
}
At the moment this script will exclude the T:\temp\Archive\cst directory, but not the T:\temp\Archive\cst\artwork directory. I'm struggling to overcome this simple thing.
I've tried the -notlike (which I didn't really expect to work) but also the -notcontains which I was hopeful of.
Can anyone offer any advice, I'm thinking it would require a regex match which I'm reading up on now, but not very familiar with.
In the future the $exclude variable will be an array of strings (directories) but at the moment just trying to get it to work with a straight string.
Try:
where {$_.fullname -notlike "$Exclude*"}
You could also try
where {$_.fullname -notmatch [regex]::Escape($Exclude) }
but the notlike apporach is easier.
When used without wildcards the -like operator does the same as the -eq operator. If you want to exclude a folder T:\temp\Archive\cst and everything below it, you need something like this:
$Exclude = 'T:\temp\Archive\cst'
Get-ChildItem -Path T:\temp\Archive -Recurse -Directory | ? {
$_.FullName -ne $Exclude -and
$_.FullName -notlike "$Exclude\*"
} | ...
-notlike "$Exclude\*" would only exclude subfolders of $Exclude, not the folder itself, and -notlike "$Exclude*" would also exclude folders like T:\temp\Archive\cstring, which may be undesired.
The -contains operator is used to check if a list of values contains a particular value. It doesn't check if a string contains a particular substring.
See Get-Help about_Comparison_Operators for further information.
Try changing
$Exclude = "T:\temp\Archive\cst"
To:
$Exclude = "T:\temp\Archive\cst\*"
This will still return the folder CST as it is a child item of Archive, but will exclude anything under cst.
Or:
$Exclude = "T:\temp\Archive\cst*
But that will also exclude anyfiles that start with "cst" under Archive. Same goes for Graimer's answer, jsut be aware of the trailing \ and if it's important to what you are doing
For those looking for a similar answer, what I ended up going with (to parse an array paths for a wildcard match):
# Declare variables
[string]$rootdir = "T:\temp\Archive"
[String[]]$Exclude = "T:\temp\Archive\cst", "T:\temp\archive\as"
[int]$days = 90
# Create Directory list minus excluded directories and their children
$list = Get-ChildItem -Path $rootdir -Recurse -Directory | where {$path = $_.fullname; -not #($exclude | ? {$path -like $_ -or $path -like "$_\*" }) }
Provides what I needed.
Thought I would add to this as I recently had a similar problem answered. You can use the -notcontains condition, but the thing that is counter intuitive is that the $exclude array needs to be at the start of the expression.
Here is an example.
If I perform the following no items are excluded and it returns "a","b","c","d"
$result = #()
$ItemArray = #("a","b","c","d")
$exclusionArray = #("b","c")
$ItemArray | Where-Object { $_ -notcontains $exclusionArray }
If I switch the variables around in the expression then it works and returns "a","d".
$result = #()
$ItemArray = #("a","b","c","d")
$exclusionArray = #("b","c")
$ItemArray | Where-Object { $exclusionArray -notcontains $_ }
I am not sure why the arrays have to be this way around to work. If anyone else can explain that would be great.
EDITED 12/12/20 - I now know that the other operation to use is "-in" as in
$_ -notin $exclusionArray