How to check file with multiple extension using Powershell? - powershell

I want to check if some file exists with multiple extension. I tried this way, it works. But I need the simple way to do this process. Is anyone can give me advice how to improve this process? Thank you so much.
$Data = "D:\Folder"
if (!(Test-Path $Data\*.que))
{
if (!(Test-Path $Data\*.pro))
{
if (!(Test-Path $Data\*.err))
{
if (!(Test-Path $Data\*.skip))
{
Write-Host "Complete"
}
else {
Write-Host "Finished, But Not Complete"
}
}
else {
Write-Host "Finished, But Not Complete"
}
}
else {
Write-Host ".pro Exist, Not Finished"
}
}
else {
Write-Host ".que Exist, Not Finished"
}

Since you have some custom messages depending on filetype, you could approach this in a few ways. One way would be to put all into a CSV and iterate over that. To simplify the code it seems logical to check if the file exists rather than not. It could look something like this.
$CSVData = #'
Extension,NotPresent,Present
que,".que Exist, Not Finished",
pro,".pro Exist, Not Finished",
err,"Finished, But Not Complete",
skip,"Complete","Finished, But Not Complete"
'# | ConvertFrom-Csv
$Data = "D:\Folder"
$CSVData | foreach {
if(Test-Path "$Data\*.$($_.extension)")
{
Write-Host $_.present
}
else
{
write-host $_.notpresent
}
}
I left the Present column empty except the one which you actually responded to. If you don't need to fill those in, you may want to check if there is anything in $_.present before Write-Host
$CSVData = #'
Extension,NotPresent,Present
que,".que Exist, Not Finished",
pro,".pro Exist, Not Finished",
err,"Finished, But Not Complete",
skip,"Complete","Finished, But Not Complete"
'# | ConvertFrom-Csv
$Data = "D:\Folder"
$CSVData | foreach {
if(Test-Path "$Data\*.$($_.extension)")
{
if($null -ne $_.present)
{
Write-Host $_.present
}
}
else
{
write-host $_.notpresent
}
}

(Get-ChildItem -Path 'D:\Folder' -File -Recurse).Extension | Group-Object
You can Get-ChildItem to get files in a directory and only grab the extension property of the returned object, then Group-Object to get a list of directories and how many of each exist

Related

Powershell If statement with multiple conditions

I have a Powershell script with If statement and multiple conditions. My code is working great but I am looking for to display which condition my object doesn't respect.
Get-ChildItem $Path -Directory -Force | ForEach-Object {
$FolderName = $_.BaseName -match $Folderpattern
$DateOK = $_.LastWriteTime -lt (Get-Date).AddDays(-3))
$Folder = $_.BaseName
if (($FolderName) -and ($DateOK) {
write-host("$Folder can be moved")
}
else {
write-host("$Folder can't be moved")
}
}
I would like to display "$folder can't be moved because it doesn't match the pattern" if it doesn't respect the $FolderName condition.
And display "$folder can't be moved because the last write time is less than 3 days" if it doesn't respect the $DateOK condition.
thanks for the help
If this is just for checking and not for keeping a log where you need those specific messages I might go for something simple where we just capture the true and false values for each of the tests.
$path = C:\temp
Get-ChildItem $Path -Directory -Force |
ForEach-Object {
[PSCustomObject]#{
Folder = $_.BaseName
LastWriteTime = $_.LastWriteTime
FolderNameTest = $_.BaseName -match 'test'
DateOKTest = $_.LastWriteTime -lt (Get-Date).AddDays(-30)
}
}
Sample Output
Folder LastWriteTime FolderNameTest DateOKTest
------ ------------- -------------- ----------
.git 06.09.2021 01:06:06 False True
.vscode 25.09.2021 10:06:11 False True
1 22.09.2021 22:30:26 False True
batch_test 02.05.2022 22:29:25 True False
cleanup 20.09.2021 10:02:51 False True
DeviceDatabase 26.09.2021 12:07:26 False True
host 22.09.2021 23:23:38 False True
move_logs 26.04.2022 19:28:59 False False
test_run 01.03.2022 22:14:14 True True
You can then pipe this to Export-Csv if you like
There are various ways to go about this; but this one is clean and easy to understand, so would be my preferred route:
Function Move-FolderConditional { # todo: give this cmdlet a better name for your context
[CmdletBinding()]
Param (
[Parameter(Mandatory, ValueFromPipeline)]
[System.IO.DirectoryInfo[]]$Path
,
[Parameter(Mandatory)]
[System.IO.DirectoryInfo]$Destination
,
# files matching this pattern get moved to the target
[Parameter(Mandatory)]
[string]$ArchivableFolderPattern
,
# Files older than this date get moved to the target
[Parameter()]
[string]$MinKeepDateUtc = (Get-Date).AddDays(-3).ToUniversalTime()
)
Process {
foreach ($directory in $Path) {
if ($directory.BaseName -notmatch $ArchivableFolderPattern) {
Write-Warning "Could not move folder '$($directory.FullName)' as the name does not match the required pattern"
continue;
}
if ($directory.LastWriteTimeUtc -ge $MinKeepDateUtc) {
Write-Warning "Could not archive folder '$($directory.FullName)' as it was last updated at '$($directory.LastWriteTimeUtc.ToString('u'))'"
continue;
}
try {
#Move-Item -Path $directory -Destination $Destination -ErrorAction Stop # Uncommend this if you actually want to move your files
Write-Information "Successfully moved '$($directory.FullName)' to '$($Destination.FullName)'"
} catch [System.Management.Automation.ItemNotFoundException] { # For this exception we'd probably check in the Begin block instead - but this is just to give the idea that we could add a try/catch if required
Write-Warning "Could not archive folder '$($directory.FullName)' the target directory does not exist: '$($Destination.FullName)'"
}
}
}
}
# Example usage
Get-ChildItem $path -Directory -Force | Move-FolderConditional -ArchivableFolderPattern '^log' -InformationAction Continue -Destination 'z:\archive\'
But other options are available (I've just included snippets to give the gist of these):
Switch Statement
switch ( $directory )
{
{$_.BaseName -notmatch $ArchivableFolderPattern}
{
Write-Warning "Could not move folder '$($_.FullName)' as the name does not match the required pattern"
break
}
{$_.LastWriteTimeUtc -ge $MinKeepDateUtc}
{
Write-Warning "Could not archive folder '$($_.FullName)' as it was last updated at '$($_.LastWriteTimeUtc.ToString('u'))'"
break
}
default
{
Write-Information "Successfully moved '$($_.FullName)' to '$($Destination.FullName)'"
}
}
Flags
[bool]$archiveFolder = $true
if ($directory.BaseName -notmatch $ArchivableFolderPattern) {
Write-Warning "Could not move folder '$($directory.FullName)' as the name does not match the required pattern"
$archiveFolder = $false
}
if ($directory.LastWriteTimeUtc -ge $MinKeepDateUtc) {
# note: this will process even if archivefolder is already false... you can use `else` or amend the condition to include `$archiveFolder -or ($directory.LastWriteTimeUtc -ge $MinKeepDateUtc)`; though if going that route it's better to use the switch statement.
Write-Warning "Could not archive folder '$($directory.FullName)' as it was last updated at '$($_.LastWriteTimeUtc.ToString('u'))'"
$archiveFolder = $false
}
if ($archiveFolder) {
Write-Information "Successfully moved '$($directory.FullName)' to '$($Destination.FullName)'"
}
Other
Or you can do combinations of the above (e.g. use the switch statement to set your flags (in which case you can optionally remove the break so that all issues are displayed).

Function not processing every step

The below snippet will jump to the correct function "ORD_LOG_PROCESS", it will CD to the path, but it will not store the variables after that. $ordfiles and every variable after that do not store. There is one file in the $ordlogpath directory, and if I do (gci $ordlogpath |% {$_.name}) at the shell it works, but via the script for some reason it will not store.
$ordlogpath = "C:\test_environment\ORD_REPO\ORD_LOGS\"
$ordlogexist = gci "C:\test_environment\ORD_REPO\ORD_LOGS\*.log"
FUNCTION ORD_LOG_PROCESS
{
cd $ordlogpath
$ordfiles = (gci $ordlogpath |% {$_.name})
FOREACH ($ordfile in $ordfiles)
{
$ordlogimport = Import-Csv $ordfile
$ordloggrep = $ordfile
exit
}
}
FUNCTION NO_FILES
{
write-host "NO FILES TO PROCESS"
EXIT
}
IF (!$ordlogexist)
{
NO_FILES
}
else
{
ORD_LOG_PROCESS
}
If you declare variables inside a function, they will be local to that function. That means that the variables do not exist outside the function.
But then.. why use functions like this at all?
Can't you simply do something like below?
$ordlogpath = "C:\test_environment\ORD_REPO\ORD_LOGS\*.log"
if (!(Test-Path -Path $ordlogpath)) {
Write-Host "NO FILES TO PROCESS"
}
else {
Get-ChildItem -Path $ordlogpath -File | ForEach-Object {
$ordlogimport = Import-Csv $_.FullName
# now do something with the $ordlogimport object,
# otherwise you are simply overwriting it with the next file that gets imported..
# perhaps store it in an array?
# it is totally unclear to me what you intend to do with this variable..
$ordloggrep = $_.Name
}
Write-Host "The log name is: $ordloggrep"
Write-Host
Write-Host 'The imported variable ordlogimport contains:'
Write-Host
$ordlogimport | Format-Table -AutoSize
}

Too many if else statements

I am trying to check if a string "03-22-2019" exists (or not and show the result in output) the file is of ".sql" or ".txt" .
execution\development\process.sql
insert.sql
production\logs.txt
restore.sql
rebuild.txt
I am trying below code but I did with too many if else. The above file path stored in the $paths variable. I need to split the path with "\" and get the last part of the path to do something else.
if ($paths -like "*.sql") {
if ($paths.Contains("\")) {
$last = $paths.Split("\")[-1] #get the last part of the path
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
} else {
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
}
} elseIf ($paths -like "*.txt") {
if ($paths.Contains("\")) {
$last = $paths.Split("\")[-1] #get the last part of the path
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
} else {
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
}
} else {
Write-Host "other file types"
}
I would make this a little simpler, below is an example to determine if a file contains the date:
$paths = #("c:\path1","c:\path2\subdir")
ForEach ($path in $paths) {
$files = Get-ChildItem -LiteralPath $path -file -include "*.sql","*.txt"
$last = ($path -split "\\")[-1] # contains the last part of the path
$output = ForEach ($file in $files) {
If (Select-String -path $file -pattern "03-22-2019") {
"$($file.fullname) contains the date."
}
else {
"$($file.fullname) does not contain the date."
}
}
}
$output # outputs whether or not a file has the date string
The outer ForEach loop loops through the paths in $paths. Inside of that loop, you can do what you need to each path $path. I used $last to store the last part of the path in the current iteration. You have not said what to do with that.
The inner ForEach checks each .txt and .sql file for the date text 03-22-2019. $output stores a string indicating whether each .txt and .sql file contains the date string.
If your paths contain the file names, then you can use the following alternatives to grab the file name (last part of the path):
$path | split-path -leaf # inside of the outer ForEach loop
# Or
$file.name # inside of the inner ForEach loop
Looks like you should start with a foreach after your $paths variable like this:
foreach ($path in $paths) {
if ($path -like "*.sql") { #Note the use of one single item in the $paths array you have
$last = $path.split("\")[-1]
$result = #Whatever method you want to use to return a DateTime object
if ($result) { #### Now, this line doesn't make any sense. Do you want to compare a date to an older date or something? Maybe something like "if ($result -ge (Get-Date).addDays(-1) )
{ # Do your stuff }
Doing something like:
if ($paths -like "*.sql")
Doesn't work because $paths is an array and you are making a string comparison and never the two shall meet. Now, if you are trying to find if a string is inside a file, you should use something like "Get-Content" or "Import-Csv"
You can use the "Get-Date" cmdlet to get many different formats for the date. Read about that here. If you are trying to compare multiple dates against multiple files, I would start with a for loop on the files like I did up there, and then a for loop on each file for an array of dates. Maybe something like this:
foreach ($path in $paths) {
foreach ($date in $dates) {
# Get the contents of a file and store it in a variable
# Search for the string in that variable and store the results in a variable
# Write to the console
} # End foreach ($date in $dates)
} # End foreach ($path in $paths)
Post some more updated code and let's see what you come up with.

Find first available file to output log to

I am working on a complete rewrite of my logging function that I use for a couple hundred scripts and I am trying to make it as robust as possible. I am trying to make it be able to create go through a very basic set of checks to find the first available log that it can write to.
I am trying to write it so it will attempt to write to each log file (in case the files have different permisisons than the directories)
Logic path
Go through each directory in the list
See if there are any logs I can append to
If there is append to them
If not, try to create a new log with # appended to it.
If cannot create a new log, move on to the next directory
This script isn't very difficult, I've written much more complex scripts, but for some reason my brain will not wrap its head around this and I keep coming up with non-robust very repetetive functions and I am trying to keep effiency and speed as the most important priority.
Function TestLogger {
$WriteTee = #{}
$WriteTee.LogName = 'WriteTee.log'
#$WriteTee.LogName = "$(((Split-Path -Leaf $script:MyInvocation.MyCommand.Definition)).BaseName).txt"
$WriteTee.LogPaths = "C:\Windows\",
'C:\Users\1151577373E\Documents\Powershell Scripts\AutoUpdater\',
"$Env:UserProfile"
#"$(Split-Path -Parent $script:MyInvocation.MyCommand.Definition)"
foreach ($Path in $WriteTee.LogPaths) {
$Path = [System.IO.DirectoryInfo]$Path
#Ensure the directory exists and if not, create it.
if (![System.IO.Directory]::Exists($Path)) {
try {
New-Item -Path $Path.Parent.FullName -Name $Path.BaseName -ItemType 'Directory' -ErrorAction Stop -Force | Out-Null
} catch {
continue
}
}
#Check to see if there are any existing logs
$WriteTee.ExistingLogs = Get-ChildItem -LiteralPath $Path -Filter "$(([System.IO.FileInfo]$WriteTee.LogName).BaseName)*$(([System.IO.FileInfo]$WriteTee.LogName).Extension)" |Sort-Object
if ($WriteTee.ExistingLogs.Count -eq 0) {
$WriteTee.LastLogName = $null
} else {
foreach ($ExistingLog in $WriteTee.ExistingLogs) {
try {
[IO.File]::OpenWrite($ExistingLog.FullName).close() | Out-Null
$WriteTee.LogFile = $ExistingLog.FullName
break
} catch {
$WriteTee.LastLogName = $ExistingLog
continue
}
}
}
#If no previous logs can be added to create a new one.
if (-not $WriteTee.ContainsKey('LogFile')) {
switch ($WriteTee.LastLogName.Name) {
{$_ -eq $Null} {
$WriteTee.ExistingLogs.count
Write-Host Create New File
}
{$_ -match '.*\[[0-9]+\]\.'} {
Write-Host AAAAAA
$WriteTee.NextLogName = $WriteTee.NextLogName.FullName.Split('[]')
$WriteTee.NextLogName = $WriteTee.NextLogName[0] + "[" + ([int]($WriteTee.NextLogName[1]) + 1) + "]" + $WriteTee.NextLogName[2]
}
default {}
}
}
#Determine if log file is available or not.
if ($WriteTee.ContainsKey('LogFile')) {
Write-Host "Function Success"
break
} else {
continue
}
}
return $WriteTee.LogFile
}
clear
TestLogger
I think I just burnt myself out yesturday, good night sleep got me going again. here is what I ended up with, really hope someone else finds some use out of it.
Function TestLogger {
$WriteTee = #{}
$WriteTee.LogName = 'WriteTee.log'
#$WriteTee.LogName = "$(((split-path -leaf $Script:MyInvocation.MyCommand.Definition)).BaseName).Log"
$WriteTee.LogPaths = 'C:\Windows\',
"C:\Users\1151577373E\Documents\Powershell Scripts\AutoUpdater\",
"$Env:UserProfile"
#"$(split-path -parent $Script:MyInvocation.MyCommand.Definition)"
Foreach ($Path in $WriteTee.LogPaths) {
If ($WriteTee.ContainsKey('LogFile')) { Break }
$Path = [System.IO.DirectoryInfo]$Path
#Ensure the directory exists and if not, create it.
If (![System.IO.Directory]::Exists($Path)) {
Try {
#Create the directory because .Net will error out if you try to create a file in a directory that doesn't exist yet.
New-Item -Path $Path.Parent.FullName -Name $Path.BaseName -ItemType 'Directory' -ErrorAction Stop -Force |Out-Null
} Catch {
Continue
}
}#End-If
#Check to see if there are any existing logs
$WriteTee.ExistingLogs = Get-ChildItem -LiteralPath $Path -Filter "$(([System.IO.FileInfo]$WriteTee.LogName).BaseName)*$(([System.IO.FileInfo]$WriteTee.LogName).Extension)" |Sort-Object
If ($WriteTee.ExistingLogs.Count -GT 0) {
ForEach ($ExistingLog in $WriteTee.ExistingLogs) {
Try {
[io.file]::OpenWrite($ExistingLog.FullName).close() |Out-Null
$WriteTee.LogFile = $ExistingLog.FullName
break
} Catch {
$WriteTee.LastLogName = $ExistingLog
Continue
}
}
}#End-If
#If no previous logs can be added to create a new one.
switch ($WriteTee.ExistingLogs.Count) {
{$PSItem -EQ 0} {
$WriteTee.TestLogFile = Join-Path -Path $Path -ChildPath $WriteTee.LogName
}
{$PSItem -EQ 1} {
$WriteTee.TestLogFile = Join-Path -Path $Path -ChildPath ($WriteTee.LastLogName.basename + '[0]' + $WriteTee.LastLogName.Extension)
}
{$PSItem -GE 2} {
$WriteTee.TestLogFile = $WriteTee.LastLogName.FullName.Split('[]')
$WriteTee.TestLogFile = ($WriteTee.TestLogFile[0] + '[' + (([int]$WriteTee.TestLogFile[1]) + 1) + ']' + $WriteTee.TestLogFile[2])
}
Default {
Write-Host "If you are looking for an explanation of how you got here, I can tell you I don't have one. But what I do have are a very particular lack of answers that I have aquired over a very long career that make these problems a nightmare for people like me."
Continue
}
}#End-Switch
#Last but not least, try to create the file and hope it is successful.
Try {
[IO.File]::Create($WriteTee.TestLogFile, 1, 'None').close() |Out-Null
$WriteTee.LogFile = $WriteTee.TestLogFile
Break
} Catch {
Continue
}
}#End-ForEach
Return $WriteTee.LogFile
}

how to use out-file with a foreach loop

$web = Get-SPWeb http://mysite
ForEach($list in $web.Lists)
{
if($list.BaseType -eq "DocumentLibrary")
{
Write-Host $list.Fields
if($list.Fields.ContainsField("marking") -eq $true)
{
Write-Host "found" $list.Title
}
}
} | Out-File test.txt
I have this code, which doesn't work due to write-host outputting to command line so obviously it won't write to the file.
How can I make it so it doesn't output to the command line but just outputs all the items found to the text file
Place your Out-File with the -Append switch after the lines you want to write, and take out theWrite-Host.
This would be a cleaner way to do it and probably faster. You're not going to get the carriage
return you're getting in your script after $list.Fields but I suspect you don't want that anyway
$doclibraries = (Get-SPWeb http://mysite).Lists | where {$_.BaseType -eq 'DocumentLibrary'}
foreach ($library in $doclibraries) {
$line = $library.Fields
if ($Line.ContainsField('marking')) {
Add-Content -Value "$line found $($library.title)" -Path test.txt
}
}