I have this file structure
In PowerShell my location is set to Folder. SubSubFolders has a lot of xml files, and I want to add a line there only if content of version.txt file is a and that line doesn't exist there already.
I was able to figure out how to change an xml file in particular SubSubFolder, but I can't do it when I start in Folder folder and and taking into consideration version
#here I need to add: only if version.txt content of xml file in parent folder is "a"
$files = Get-ChildItem -Filter *blah.xml -Recurse | Where{!(Select-String -SimpleMatch "AdditionalLine" -Path $_.fullname -Quiet)} | Format-Table FullName
foreach($file in $files)
{
(Get-Content $file.FullName | Foreach-Object { $_
if ($_ -match "AdditionalLineAfterThisLine")
{
"AdditionalLine"
}
}) | Set-Content $file.FullName
}
If I understand you correctly, you're looking for the following:
$files = (
Get-ChildItem -Filter *blah.xml -Recurse |
Where-Object{
-not ($_ | Select-String -SimpleMatch "AdditionalLine" -Quiet) -and
(Get-Content -LiteralPath "$($_.DirectoryName)/../version.txt") -eq 'a'
}
).FullName
Note that the assumption is that the version.txt file contains just one line. If it contains multiple lines, the -eq 'a' operation would act as a filter and return all lines whose content is 'a', which in the implied Boolean context of -and would yield $true if one or more such lines, potentially among others, exist.
Related
I have several csv and txt files in a directory with data in them. I need to truncate the data from all of these files but leave the header in each.
You can use following script - it should work, if all files have more than one line...
$files = dir .\* -include ('*.csv', '*.txt')
foreach ($file in $files) {
$firstline = (get-content $file)[0]
set-content $file -Value $firstline
}
You do not need to read the whole file in order to just capture the first line..
Get-ChildItem -Path 'D:\Test' -File | Where-Object { $_.Extension -match '\.(csv|txt)'} | ForEach-Object {
# only read the first line using -TotalCount
($_ | Get-Content -TotalCount 1) | Set-Content -Path $_.FullName
}
The above could produce empty or whitespace only files if the top line is empty or only contains whitespaces..
Perhaps then the best option to quickly truncate these files to the top NON-EMPTY line would be:
Get-ChildItem -Path 'D:\Test' -File | Where-Object { $_.Extension -match '\.(csv|txt)'} | ForEach-Object {
$newcontent = switch -Regex -File $_.FullName {
'\S' { $_ ; break} # output the first line that is not empty or whitespace-only and exit the switch
}
# write back to the file
$newcontent | Set-Content -Path $_.FullName
}
P.S. Using -Filter as parameter on Get-ChildItem would work faster, but unfortunately, the filter can only be used for ONE file pattern only, like '*.csv'.
If you need recursion (search subfolders as well), then you could user the -Include parameter which accepts an array of file patterns. However, for that to work, you also need to add switch -Recurse OR have the path end in \*.
-Include is not as fast as -Filter, just about the same speed as by using a Where-Object clause in the examples above
I've been struggling with this all day now. I'm trying to delete any files in a given directory if the filenames match a word in a CSV list.
I have imported the CSV list as text then try to match words with the filenames in the target directory, however I'm not sure which operator to use or how to go about this. Obviously 'Match' is not a cmdlet.
#store csv file as variable
$CSVList = Get-content -Delimiter (',') -path "C:\Users\Leeds TX 11\Desktop\Test folder\Watchfolder\DAZN Production Numbers - purgelist.csv"
write-host $CSVList
#delete if word matches .csv entry
$targetdirectory = "C:\Users\Leeds TX 11\Desktop\Test folder\AVMedia"
$Files = Get-childItem $targetdirectory | Match ("\w") $CSVList | remove-item -whatif
write-host $Files
Just add a Where-Object to the pipe where you check whether the Name property is -In the $CSVList:
# ...
$Files = Get-ChildItem $targetdirectory | Where-Object Name -In $CSVList | Remove-Item -whatif
Note: I consider the filenames within your CSV contains a file extension. If not, you want to change Where-Object Name to Where-Object BaseName.
Edit. Since you are using PowerShell Version 2, you have to use -contains and swap the variables:
$Files = Get-ChildItem $targetdirectory | Where-Object {$CSVList -contains $_.BaseName} | Remove-Item -whatif
try Something like this
#store csv file as variable and take first column
$CSVList = (import-csv "c:\temp\missing.csv" -Header col1).col1
#delete if word matches .csv entry
$targetdirectory = "C:\temp2"
$Files = Get-childItem $targetdirectory -Recurse | Where-Object {$CSVList -contains $_.BaseName } | remove-item -whatif
write-host $Files
The code below will copy content from text file to a file in directory but it will overwrite the existing file content:
Get-ChildItem $FilePath -Recurse -Include *.docx,*folder\abc\source.txt | ForEach-Object {Copy-Item $_.FullName -Destination C:\destination.txt}
I want to append all the content to a single file.
Use Get-Content and Add-Content instead of Copy-Item:
Get-ChildItem $FilePath -Recurse -Include *.txt | ? {
$_.FullName -like '*folder\abc\source.txt'
} | Get-Content | Add-Content 'C:\destination.txt'
Edit: As #BaconBits correctly pointed out in the comments to your question you cannot simply concatenate Word documents and text files, because the former are binary files (zip archives containing a bunch of XML files, actually). However, if you want just the (text) content of the document appended, you could do something like this:
$wd = New-Object -COM 'Word.Application'
Get-ChildItem $FilePath -Recurse | ? {
$_.Extension -eq '.docx' -or
$_.FullName -like '*folder\abc\source.txt'
} | % {
$path = $_.FullName
switch ($_.Extension) {
'.docx' {
$doc = $wd.Documents.Open($path)
$doc.Content.Text
$doc.Close()
}
'.txt' { Get-Content $path }
}
} | Add-Content 'C:\destination.txt'
$wd.Quit()
Note: I was unable to get Get-ChildItem to work with partial paths in the -Include parameter, so I moved those conditions to a Where-Object filter.
How can I change the following code to look at all the .log files in the directory and not just the one file?
I need to loop through all the files and delete all lines that do not contain "step4" or "step9". Currently this will create a new file, but I'm not sure how to use the for each loop here (newbie).
The actual files are named like this: 2013 09 03 00_01_29.log. I'd like the output files to either overwrite them, or to have the SAME name, appended with "out".
$In = "C:\Users\gerhardl\Documents\My Received Files\Test_In.log"
$Out = "C:\Users\gerhardl\Documents\My Received Files\Test_Out.log"
$Files = "C:\Users\gerhardl\Documents\My Received Files\"
Get-Content $In | Where-Object {$_ -match 'step4' -or $_ -match 'step9'} | `
Set-Content $Out
Give this a try:
Get-ChildItem "C:\Users\gerhardl\Documents\My Received Files" -Filter *.log |
Foreach-Object {
$content = Get-Content $_.FullName
#filter and save content to the original file
$content | Where-Object {$_ -match 'step[49]'} | Set-Content $_.FullName
#filter and save content to a new file
$content | Where-Object {$_ -match 'step[49]'} | Set-Content ($_.BaseName + '_out.log')
}
To get the content of a directory you can use
$files = Get-ChildItem "C:\Users\gerhardl\Documents\My Received Files\"
Then you can loop over this variable as well:
for ($i=0; $i -lt $files.Count; $i++) {
$outfile = $files[$i].FullName + "out"
Get-Content $files[$i].FullName | Where-Object { ($_ -match 'step4' -or $_ -match 'step9') } | Set-Content $outfile
}
An even easier way to put this is the foreach loop (thanks to #Soapy and #MarkSchultheiss):
foreach ($f in $files){
$outfile = $f.FullName + "out"
Get-Content $f.FullName | Where-Object { ($_ -match 'step4' -or $_ -match 'step9') } | Set-Content $outfile
}
If you need to loop inside a directory recursively for a particular kind of file, use the below command, which filters all the files of doc file type
$fileNames = Get-ChildItem -Path $scriptPath -Recurse -Include *.doc
If you need to do the filteration on multiple types, use the below command.
$fileNames = Get-ChildItem -Path $scriptPath -Recurse -Include *.doc,*.pdf
Now $fileNames variable act as an array from which you can loop and apply your business logic.
Other answers are great, I just want to add... a different approach usable in PowerShell:
Install GNUWin32 utils and use grep to view the lines / redirect the output to file http://gnuwin32.sourceforge.net/
This overwrites the new file every time:
grep "step[49]" logIn.log > logOut.log
This appends the log output, in case you overwrite the logIn file and want to keep the data:
grep "step[49]" logIn.log >> logOut.log
Note: to be able to use GNUWin32 utils globally you have to add the bin folder to your system path.
I need to check and return files that exist in the filesystem, but are not listed in a given text file. For instance, my text file (sample.txt) will contain paths like:
\\SharedDrive\Data\DevS\Common\app_name\subfolder1\Archive\Archive1.vbproj
\\SharedDrive\Data\DevS\NotCommon\app_name\subfolder1\user\WebApp.vbproj
\\SharedDrive\Data\DevS\UnCommon\app_name\subfolder1\Manager\Managerial.vbproj
It happens that there are VB project files that exists on the drive but are not among the list, which i want to return along with their full path. For instance:
\\SharedDrive\Data\DevS\Common\app_name\subfolder2\Windows\SharedArchive.vbproj
\\SharedDrive\Data\DevS\NotCommon\app_name2\subfolder1\user2\WebApp2.vbproj
I tried this:
$log = "e:\pshell\notExists.log"
Get-Content "e:\pshell\Sample.txt" | Where-Object {
#Keep only paths that does not exists
!(Test-Path $_)
} | Set-Content $log
but this does the other way around.
Try this:
$baseDir = "\\SharedDrive\Data\DevS"
$log = "..."
$paths = Get-Content sample.txt
Get-ChildItem $baseDir -Recurse -Filter *.vbproj | ? {
-not $_.PSIsContainer -and $paths -notcontains $_.FullName
} | % { $_.FullName } | Set-Content $log