I have a PowerShell code where I want to check if in folder is file containing certain pattern.
If there is a file with certain pattern then I want to continue the work.
I have it as this:
$path = "c:\test\"
$Pattern = "1044"
$1044 = Get-ChildItem $Path | Where {$_.Name -match $Pattern}
$strFileName = "C:\test\" + $1044
if (Test-Path $strFileName) {
...
}
The issue is that the code keeps running even though the file with the pattern isn't there.
I printed the $1044 and it says
Can't find file
I printed also out the if condition and it says:
Can't find file True
That's why the code is running but I really don't understand why.
The statement $1044 = Get-ChildItem $Path | Where {$_.Name -match $Pattern} could return any number of results (zero, one, or more).
If it returns zero results the variable $1044 will be empty, so $strFileName = "C:\test\" + $1044 will evaluate to just C:\test\, which Test-Path will (correctly) report as existing.
If it returns more than one result he variable $1044 will contain a list of file or folder objects. Doing $strFileName = "C:\test\" + $1044 will mangle this list into a space-separated string of the file/folder names and append that to the base path (meaning you'll get something like C:\test\1044foo bar1044 1044_baz instead of C:\test\1044foo, C:\test\bar1044, C:\test\1044_baz). This path doesn't exist, so Test-Path will always evaluate to false in that situation.
Besides, you don't need to build $strFileName by string concatenation in the first place (unless your base path is different from the path used with Get-ChildItem), because the objects returned by Get-ChildItem already have a property with the full path (FullName). And you also don't need to test the paths for existence, because Get-ChildItem wouldn't have returned an item if it didn't exist.
Change your code to something like this:
$1044 = Get-ChildItem $Path | Where-Object {$_.Name -match $Pattern}
if ($1044) {
...
}
and it should do what you expect, because PowerShell evaluates $1044 to $false if it's empty and to $true if it isn't.
If your patern is not complicated, you can use test-path with wildcard, not necessary to use get-childitem
$Path="C:\test\*"
$Patern="*1044*"
if (Test-Path -Path $Path -Filter $Patern -PathType Leaf)
{
"File whith patern exist"
}
Related
I have a base folder as:
D:\St\Retail\AMS\AMS\FTP-FromClient\AMS
It contains various folders of dates:
2022-04-01
2022-04-02
...
...
2022-02-02
2021-05-05
2019-04-12
And each of these folders contains own files inside the folder. So, I want to retrieve all the filename inside the folder if it has 2022-04. So if the folder has '2022-04' as the base name ,I need to retreive all the file inside the folder like '2022-04-01','2022-04-02','2022-04-03'. The way I tried is:
cls
$folerPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$files = Get-ChildItem $folerPath
[System.Collections.ArrayList]$data = #()
foreach ($f in $files) {
$a = Get-ChildItem $f.FullName
foreach ($inner in $a) {
echo $inner.FullName
$outfile = $inner.FullName -match '*2022-04*'
$datepart = $inner.FullName.split('\')[-1]
if ($outfile) {
$data.add($datepart + '\' + $inner.Name.Trim())
}
}
}
My final $data may contains like this:
2022-04-01/abc.txt
2022-04-02/cde.pdf
2022-04-03/e.xls
You can do this by first collecting the directories you want to explore and then loop over these to get the files inside.
Using a calculated property you can output in whatever format you like:
$folderPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$data = Get-ChildItem -Path $folderPath -Filter '2022-04*' -Directory | ForEach-Object {
$dir = $_.Name
(Get-ChildItem -Path $_.FullName -File |
Select-Object #{Name = 'FolderFile'; Expression = {'{0}\{1}' -f $dir, $_.Name}}).FolderFile
}
After this, $data would be a string array with this content:
2022-04-01\abc.txt
2022-04-02\cde.pdf
2022-04-03\e.xls
By using wildcards for both directory and file name, you only need a single Get-ChildItem call:
$folderPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$folderDate = '2022-04'
[array] $data = Get-ChildItem "$folderPath/$folderDate*/*" -File | ForEach-Object{
# Join-Path's implicit output will be captured as an array in $data.
Join-Path $_.Directory.Name $_.Name
}
$data will be an array of file paths like this:
2022-04-01\abc.txt
2022-04-02\cde.pdf
2022-04-03\e.xls
Notes:
[array] $data makes sure that the variable always contains an array. Otherwise PowerShell would output a single string value when only a single file is found. This could cause problems, e. g. when you want to iterate over $data by index, you would iterate over the characters of the single string instead.
To make this answer platform-independent I'm using forward slashes in the Get-ChildItem call which work as path separators under both Windows and *nix platforms.
Join-Path is used to make sure the output paths use the expected default path separator (either / or \) of the platform.
The task is to track the execution of SQL backups. One of the tracking mechanisms is to browse the directory where the backups are saved and if the file is missing, you can send an email with the full address of the directory where the file is missing. This is what I am trying to implement with Powershell script, but for some reason my script doesn't work and doesn't give me the information I need.
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)}
$fileExists = Test-Path $list
If ($fileExists)
{
#Do Nothing
}
Else
{
$list | Select DirectoryName
}
Can anyone help?
I suppose what you need is to test each file or path individually. You take Get-ChildItem with recurse, so it returns multiple files and stores them in $list.
If you do something like
Foreach ($item in $list) {
$fileexists = Test-Path $item
If ($fileexists -eq $false) {
do something }
}
You should be good to go. This would cycle through all items and does whatever you need to be done. If you compare against $false, you wouldn't need the else statement, and you could also just put "Test-Path" into the if-statement like
If (Test-Path $item -eq $false) {}
Edit: Sorry I accidentally posted the answer before finishing it lol
Also, as stackprotector correctly points out, Get-ChildItem can only retrieve items that exist, because how should it detect missing files.
If you're wanting to check for something that is missing or doesn't exist, you need to start with a known condition, e.g.: either the server names or expected file or directory names.
If you know that, then you can create a static list (or dynamically query a list from Active Directory for your SQL servers or something (assuming the backup file names correspond to the server names)) and then check the files that were created and output the missing ones for triage.
Here is a modification to your script (essentially the opposite of what you did) that might point you in the right direction:
## List of expected files
$ExpectedFiles = #(
'File1.bak',
'File2.bak',
'File3.bak',
'File4.bak'
'...'
)
## Get a list of created files
$list = Get-ChildItem -Path network_path_to_share -Recurse -File | Where-Object {$_.DirectoryName -like '*FULL*' -and $_.LastWriteTime -ge (Get-Date).AddDays(-6)} | Select -ExpandProperty Name
## Check whether each expected file exists in the array of backups that actually were created
foreach ($file in $ExpectedFiles) {
if (-not(Test-Path $list)) {
"$($file) is missing!"
}
}
I am having a strange problem in Powershell (Version 2021.8.0) while creating folders and naming them. I start with a number of individual ebook files in a folder that I set using Set-Location. I use the file name minus the extension to create a new folder with the same name as the e-book file. The code works fine the majority of the time with various file extensions I have stored in an array beginning of the code.
What's happening is that the code creates the proper folder name the majority of the time and moves the source file into the folder after it's created.
The problem is, if the last letter of the source file name, on files with the extension ".epub" end in an "e", then the "e" is missing from the end of the created folder name. I thought that I saw it also drop "r" and "p" but I have been unable to replicate that error recently.
Below is my code. It is set up to run against file extensions for e-books and audiobooks. Please ignore the error messages that are being generated when files of a specific type don't exist in the working folder. I am just using the array for testing and it will be filled automatically later by reading the folder contents.
This Code Creates a Folder for Each File and moves the file into that Folder:
Clear-Host
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
Set-Location $SourceFileFolder
$MyArray = ( "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt" )
Foreach ($FileExtension in $MyArray) {
Get-ChildItem -Include $FileExtension -Name -Recurse | Sort-Object | ForEach-Object { $SourceFileName = $_
$NewDirectoryName = $SourceFileName.TrimEnd($FileExtension)
New-Item -Name $NewDirectoryName -ItemType "directory"
$OriginalFileName = Join-Path -Path $SourceFileFolder -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $NewDirectoryName -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $SourceFileFolder -ChildPath $DestinationFilename
Move-Item $OriginalFileName -Destination $DestinationFilename
}
}
Thanks for any help you can give. Driving me nuts and I am pretty sure it's something that I am doing wrong, like always.
String.TrimEnd()
Removes all the trailing occurrences of a set of characters specified in an array from the current string.
TrimEnd method will remove all characters that matches in the character array you provided. It does not look for whether or not .epub is at the end of the string, but rather it trims out any of the characters in the argument supplied from the end of the string. In your case, all dots,e,p,u,b will be removed from the end until no more of these characters are within the string. Now, you will eventually (and you do) remove more than what you intended for.
I'd suggest using EndsWith to match your extensions and performing a substring selection instead, as below. If you deal only with single extension (eg: not with .tar.gz or other double extensions type), you can also use the .net [System.IO.Path]::GetFileNameWithoutExtension($MyFileName) method.
$MyFileName = "Teste.epub"
$FileExt = '.epub'
# Wrong approach
$output = $MyFileName.TrimEnd($FileExt)
write-host $output -ForegroundColor Yellow
#Output returns Test
# Proper method
if ($MyFileName.EndsWith($FileExt)) {
$output = $MyFileName.Substring(0,$MyFileName.Length - $FileExt.Length)
Write-Host $output -ForegroundColor Cyan
}
# Returns Tested
#Alternative method. Won't work if you want to trim out double extensions (eg. tar.gz)
if ($MyFileName.EndsWith($FileExt)) {
$Output = [System.IO.Path]::GetFileNameWithoutExtension($MyFileName)
Write-Host $output -ForegroundColor Cyan
}
You're making this too hard on yourself. Use the .BaseName to get the filename without extension.
Your code simplified:
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
$MyArray = "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt"
(Get-ChildItem -Path $SourceFileFolder -Include $MyArray -File -Recurse) | Sort-Object Name | ForEach-Object {
# BaseName is the filename without extension
$NewDirectory = Join-Path -Path $SourceFileFolder -ChildPath $_.BaseName
$null = New-Item -Path $NewDirectory -ItemType Directory -Force
$_ | Move-Item -Destination $NewDirectory
}
I want to create a script that searches through a directory for specific ".txt" files with the Get-ChildItem cmdlet and after that it copies the ".txt" to a location I want. The hard part for me is to extract specific .txt files string from the array. So basically I need help matching specific files names in the array. Here is an example of the array I'm getting back with the following cmdlet:
$arrayObject = (Get-ChildItem -recurse | Where-Object { $_.Name -eq "*.txt"}).Name
The arrayobject variable is something like this:
$arrayobject = "test.2.5.0.txt", "test.1.0.0.txt", "test.1.0.1.txt",
"test.0.1.0.txt", "test.0.1.1.txt", "test.txt"
I want to match my array so it returns the following:
test.2.5.0.txt, test.1.0.0.txt, test.1.0.1.txt
Can someone help me with Regex to match the above file names from the $arrayObject?
As you already add the -Recurse parameter to Get-ChildItem, you can also use the -Include parameter like this:
$findThese = "test.2.5.0.txt", "test.1.0.0.txt", "test.1.0.1.txt"
$filesFound = (Get-ChildItem -Path 'YOUR ROOTPATH HERE' -Recurse -File -Include $findThese).Name
P.S. without the -Recurse parameter you need to add \* to the end of the rootfolder path to be able to use -Include
Maybe something like:
$FileList = Get-ChildItem -path C:\TEMP -Include *.txt -Recurse
$TxtFiles = 'test1.txt', 'test3.txt', 'test9.txt'
Foreach ($txt in $TxtFiles) {
if ($FileList.name -contains $txt) {Write-Host File: $Txt is present}
}
A general rule: Filter as left as possible! Less objects to be processed, less resources to be used, faster to be processed!
Hope it helps!
Please try to clarify what the regex should match.
I created a regex which matches out of the given filenames only the files you wanted to retrieve:
"*.[1-9].[0-9].[0-9].txt"
You can tryout the small check I wrote.
ForEach($file in $arrayobject){
if($file -LIKE "*.[1-9].[0-9].[0-9].txt"){
Write-Host $file
}}
I think the "-LIKE" operator would be better to check if a string matches a regex.
Let me know if this helps.
Sorry for the late reply. Just got back in the office today. My question has been misinterpreted but that's my fault. I wasn't clear what I really want to do.
What I want to do is search through a directory and retrieve/extract in my case the (major)version of a filename. So in my case file "test.2.5.0.txt" would be version 2.5.0. After that I will get the MajorVersion and that's 2. Then in an If statement I would check if it's greater or equal to 1 and then copy it to a specific destination. To add some context to it. It's nupkg files and not txt. But I figured it out. This is code:
$sourceShare = "\\server1name\Share\txtfilesFolder"
destinationShare = "\\server2name\Share\txtfilesFolder"
Get-ChildItem -Path $sourceShare `
-Recurse `
-Include "*.txt" `
-Exclude #("*.nuspec", "*.sha512") `
| Foreach-Object {
$fileName = [System.IO.Path]::GetFileName($_)
[Int]$majorVersion = (([regex]::Match($fileName,"(\d+(.\d+){1,})" )).Value).Split(".")[0]
if ($majorVersion -ge 1)
{
Copy-Item -Path $_.FullName `
-Destination $destinationShare `
-Force
}
}
If you have anymore advice. Let me know. I would be great to extract the major version without using the .Split method
Grtz
1. Code Description alias how it is intended to work
User enters a path to a directory in PowerShell. Code checks if any folder within the declared directory contains no data at all. If so, the path of any empty folder will be shown on the prompt to the user and eventually removed from the system.
2. The Issue alias what I am struggling with
The code I just wrote doesn't count the depth of a folder hierarchy as I would expect (the column in the output table is blank). Besides that, the program works okay - I've still got to fix the issue where my code removes empty parent directories first and child directories later, which of course will cause an error in PowerShell; for instance, take
C:\Users\JohnMiller\Desktop\Homework
where Homework consists of Homework\Math\School Project and Homework\Computer Science\PowerShell Code. Note that all directories are supposed to be empty with the exception of PowerShell Code, the folder containing this script. (Side note: A folder is considered empty when no file dwells inside. At least that's what my code is based on for now.)
3. The Code
# Delete all empty (sub)folders in [$path]
[Console]::WriteLine("`n>> Start script for deleting all empty (sub)folders.")
$path = Read-Host -prompt ">> Specify a path"
if (test-path $path)
{
$allFolders = Get-ChildItem $path -recurse | Where {$_.PSisContainer -eq $True}
$allEmptyFolders = $allFolders | Where-Object {$_.GetFiles().Count -eq 0}
$allEmptyFolders | Select-Object FullName,#{Name = "FolderDepth"; Expression = {$_.DirectoryName.Split('\').Count}} | Sort-Object -descending FolderDepth,FullName
[Console]::WriteLine("`n>> Do you want do remove all these directories? Validate with [True] or [False].") #'#
$answer = Read-Host -prompt ">> Answer"
if ([System.Convert]::ToBoolean($answer) -eq $True)
{
$allEmptyFolders | Remove-Item -force -recurse
}
else
{
[Console]::WriteLine(">> Termination confirmed.`n")
exit
}
}
else
{
[Console]::WriteLine(">> ERROR: [$($path)] is an invalid directory. Program terminates.`n")
exit
}
The depth-count problem:
Your code references a .DirectoryName property in the calculated property passed to Select-Object, but the [System.IO.DirectoryInfo] instances output by Get-ChildItem have no such property. Use the .FullName property instead:
$allEmptyFolders |
Select-Object FullName,#{Name='FolderDepth'; Expression={$_.FullName.Split('\').Count}} |
Sort-Object -descending FolderDepth,FullName
Eliminating nested empty subfolders:
To recap your problem with a simple example:
If c:\foo is empty (no files) but has empty subdir. c:\foo\bar, your code outputs them both, and if you then delete c:\foo first, deleting c:\foo\bar next fails (because deleting c:\foo also removed c:\foo\bar).
If you eliminate all nested empty subdirs. up front, you not only declutter what you present to the user, but you can then safely iterative of the output and delete one by one.
With your approach you'd need a 2nd step to eliminate the nested empty dirs., but here's a depth-first recursive function that omits nested empty folders. To make it behave the same way as your code with respect to hidden files, pass -Force.
function Get-RecursivelyEmptyDirectories {
[cmdletbinding()]
param(
[string] $LiteralPath = '.',
[switch] $Force,
[switch] $DoNotValidatePath
)
$ErrorActionPreference = 'Stop'
if (-not $DoNotValidatePath) {
$dir = Get-Item -LiteralPath $LiteralPath
if (-not $dir.PSIsContainer) { Throw "Not a directory path: $LiteralPath" }
$LiteralPath = $dir.FullName
}
$haveFiles = [bool] (Get-ChildItem -LiteralPath $LiteralPath -File -Force:$Force | Select-Object -First 1)
$emptyChildDirCount = 0
$emptySubdirs = $null
if ($childDirs = Get-ChildItem -LiteralPath $LiteralPath -Directory -Force:$Force) {
$emptySubDirs = New-Object System.Collections.ArrayList
foreach($childDir in $childDirs) {
if ($childDir.LinkType -eq 'SymbolicLink') {
Write-Verbose "Ignoring symlink: $LiteralPath"
} else {
Write-Verbose "About to recurse on $($childDir.FullName)..."
try { # If .AddRange() fails due to exceeding the array list's capacity, we must fail too.
$emptySubDirs.AddRange(#(Get-RecursivelyEmptyDirectories -DoNotValidatePath -LiteralPath $childDir.FullName -Force:$Force))
} catch {
Throw
}
# If the last entry added is the child dir. at hand, that child dir.
# is by definition itself empty.
if ($emptySubDirs[-1] -eq $childDir.FullName) { ++$emptyChildDirCount }
}
} # foreach ($childDir ...
} # if ($childDirs = ...)
if (-not $haveFiles -and $emptyChildDirCount -eq $childDirs.Count) {
# There are no child files and all child dirs., if any, are themselves
# empty, so we only output the input path at hand, as the highest
# directory in this subtree that is empty (save for empty descendants).
$LiteralPath
} else {
# This directory is not itself empty, so output the (highest-level)
# descendants that are empty.
$emptySubDirs
}
}
Tips regarding your code:
Get-ChildItem -Directory is available in PSv3+, which is not only shorter but also more efficient than Get-ChildItem | .. Where { $_.PSisContainer -eq $True }.
Use Write-Host instead of [Console]::WriteLine
[System.Convert]::ToBoolean($answer) only works with the culture-invariant string literals 'True' and 'False' ([bool]::TrueString and [bool]::FalseString, although case variations and leading and trailing whitespace are allowed).