What I need to do is to take a number of text files (csv, all in one directory) and create files where the entire output is reversed line by line, but keeping the header at the top instead of ending up at the bottom.
I am able to take one file (by name) and copy the first line and create a new file with just that line in it. Then I take the original file minus the first line, read it into an array and reverse it. I then append that to the file that only has the header. It works fine, except for the output name, which I'd like to be [file-REV.csv] but so far I've only gotten to [file.csv-REV]...
So, once I had that working, I thought it was time to have the program find all of the csv's in the directory and loop through them, creating a reverse file for each.
This is what I have so far:
cd c:\users\$([Environment]::UserName)\working
$Path = "c:\users\$([Environment]::UserName)\working\"
ForEach ($file in Get-ChildItem -Filter *.csv) {
Get-Content $file -totalcount 1 | Set-Content .\$file-REV.csv
$flip = (get-content | select -Skip 1)
[array]::Reverse($flip)
$flip | add-content "$file-REV.csv"
}
Here is the message I receive when executing the script:
cmdlet Get-Content at command pipeline position 1
Supply values for the following parameters:
Path[0]:
I've tried to put in the entire path Get-Content -Path c:\users\jmurphy\working\ and then it complains that it can't find the entire path.
Couple things. First you are defining the folder to work in a variable, so use that in the Get-ChildItem. (I change the name to $Folder out of habit because $Path is already used variable in the Environment scope. Also $env: is a quicker way to read the variables out of the Environment scope.).
$Folder = "C:\Users\$env:UserName\working\"
$Files = Get-ChildItem $Folder -Filter *.csv
Second, you'll just want to use the Fullname property from what's returned from Get-ChildItem because that's the full path of each file.
ForEach ($File in $Files) {
So you'll want to use that full path to the file in your Get-Content
Get-Content $File.Fullname -totalcount 1 | Set-Content .\$($file.Basename)-REV.csv
and you'll want to use $File again as the path to the file when you call Get-Content again:
$Flip = (Get-Content $File.Fullname | Select -Skip 1)
Basename is the property from Get-Childitem with just the filename without the extension. You can force an evaluation of a property of a variable inside double quotes by enclosing in $().
$Flip | Add-Content "$($file.Basename)-REV.csv"
All together the script should look like this:
$Folder = "C:\Users\$env:UserName\working\"
$Files = Get-ChildItem $Folder -Filter *.csv
ForEach ($File in $Files) {
Get-Content $File.Fullname -totalcount 1 | Set-Content .\$($file.Basename)-REV.csv
$Flip = (Get-Content $File.Fullname | select -Skip 1)
[array]::Reverse($Flip)
$Flip | Add-Content "$($file.basename)-REV.csv"
}
Joe, you are doing great at learning PowerShell. You just need to harness the power of object orientation a little more.
You already know that Get-ChildItem will return an object about the file(s). What you need to know are the members included in that object. Use Get-Member to find that out.
$file = Get-ChildItem .\t.csv
$file | Get-Member
Once you find an interesting member, see what its value is.
$file.Name
$file.Extension
$file.BaseName
From this, you can construct a new file name.
$newFilename = $file.Basename + '-REV' + $file.Extension
Put this before the Get-Content line. Then use $newFilename with Add-Content.
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
Data mapping project, in house system to new vendor system. First step is find all the occurrences of current database field names (or column names to be precise) in the C# .cs source files. Trying to use Powershell. Have recently created PS searches with Get-ChildItem and Select-String that work well but the search string array was small and easily hard coded inline. But the application being ported has a couple hundred column names and significant amounts of code. So armed with a text file of all the column names Pipleline would seem like a god tool to create a the basic cross ref for further analysis. However, I was not able to get the Pipeline to work with an external variable anyplace other than first step. Trying using -PipelineVariable, $_. and global variable. Did not find anything specific after lots of searching. P.S. This is my first question to StackoOverflow, be kind please.
Here is what I hoped would work but do dice so far.
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr = [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force |
Select-String $s | Select-Object Path, LineNumber, line | Export-csv $outputfile
}
Did find that this will print the list one time but not twice. In fact it seems using the variable in this way results in processing simply skipping any further pipeline steps.
foreach ($s in $arr) {Write-Host $s | Write $s}
If it isn't possible to do this in Powershell easily my fallback is to do with C# although would much rather get the level up with PowerShell if anyone can point me to the correct understanding of how to do things in the Pipepline, or alternatively construct an equivalent function. Seems like such a natural fit for Powershell.
Thanks.
You're calling Export-csv $outputfile in a loop, which rewrites the whole file in every iteration, so that only the last iteration's output will end up in the file.
While you could use -Append to iteratively append to the output file, it is worth aking a step back: Select-String can accept an array of patterns, causing a line that matches any of them to be considered a match.
Therefore, your code can be simplified as follows:
$inputFile = 'C:\DataColumnsNames.txt'
$outputFile = 'C:\DataColumnsUsages.txt'
Get-ChildItem C:\ProjectFolder -Filter *.cs -Recurse -Force -ea SilentlyContinue |
Select-String -Pattern (Get-Content $inputFile) |
Select-Object Path, LineNumber, line |
Export-csv $outputfile
-Pattern (Get-Content $inputFile) passes the lines of input file $inputFile as an array of patterns to match.
By default, these lines are interpreted as regexes (regular expressions); to ensure that they're treated as literals, add -SimpleMatch to the Select-String call.
This answer to a follow-up question shows how to include the specific pattern among the multiple ones passed to -Pattern that matched on each line in the output.
I think you want to append each occurrence to the csv file. And you need to get the content of the file. Try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | Export-csv -Append -Path "$outputfile"
}
}
-Append was not introduced before powershell v3.0 (Windows 8) then try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | ConvertTo-CSV -NoTypeInformation | Select-Object -Skip 1 | Out-File -Append -Path "$outputfile"
}
}
I have an exported CSV file with the below table
"Directory","BaseName"
"E:\Movie Files\Movie (2000)","Movie"
"E:\Movie Files\Movie (2001)","Movie 2"
My code is very close to what I want and that is basically rename the cover.jpg in each folder to the basename of the movie and add -poster.jpg after it, e.g. Movie-poster.jpg, Movie 2-poster.jpg.
It works for the first folder but every folder after this uses the same basename from the first folder. It doesn't recurse for every folder within the root location like I thought it would. So for Movie (2001) I end up having Movie-poster.jpg, not Movie 2-poster.jpg.
This is the code that's not quite there:
$lines = Import-Csv 'E:\Output.csv'
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
Expected results as mentioned above is that each cover.jpg in each folder and sub-folders will be renamed to match the basename according to their locations.
You end up with all covers being renamed to Movie-poster.jpg because your code renames all covers under the current working directory (.) in the first iteration of your loop, so there are no files with the string "Cover" in their name left when the loop goes into the second iteration.
Process the directories from your CSV instead of processing the current working directory for each of them, and use the current object from the pipeline ($_) for renaming the files. For the latter you also must replace the parentheses with curly brackets.
Change this:
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
into this:
foreach ($line in $lines) {
Get-ChildItem -Path $line.Directory -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName {$_.BaseName + '-poster.jpg'}
}
Thank you Ansgar for the comment. It didn't work in my instance, what it did was rename ALL the files (including those that already had the correct naming convention) to be cover-poster.jpg however the code did point me in the right direction with regards to $line.Directory.
The code that worked for me in the end was this;
# Renames Cover.jpg to match BaseName file with basename-poster.jpg
$lines = Import-Csv 'E:\Output.csv'
ForEach ($line in $lines) {
Get-ChildItem -Path $line.Directory -file -filter '*Cover*' -Recurse | Rename-Item -NewName {$line.BaseName + '-poster.jpg'}
}
I was wondering how I would go about combining specific files together using powershell. Example: I want to take EDIDISC.UPD EDIACCA.UPD EDIBRUM.UPD ETC ETC ETC ETC and Combine the contents of these files together and make a new file named A075MMDDYY.UPD. Now I would want it to be able to be run by whomever has the .UPD files on their network drive. Such as example: Mine would be in N:\USERS\Kevin, someone else's may be in N:\USERS\JohnDoe.
So far I only have:
Param (
$path = (Get-Location).Path,
$filetype = "*.UPD",
$files = (Get-ChildItem -Filter $filetype),
$Newfile = $path + "\Newfile.UPD"
)
$files | foreach { Get-Content $_ | Out-File -Append $Newfile -Encoding ascii }
Focusing just on the aspect of concatenating (catting) the contents of multiple files to form a new file, assuming the current directory (.):
$dir = '.'
$newfile ="$dir/Newfile.UPD"
Get-Content "$dir/*.UPD" -Exclude (Split-Path -Leaf $newFile) |
Out-File -Append -Encoding Ascii $newFile
You can pass a wildcard expression directly to Get-Content's (implied) -Path parameter in order to retrieve the contents of all matching files.
Since the output file is placed in the same dir. and matches the wildcard expression too, however, it must be excluded from matching, by filename, hence the -Exclude (Split-Path -Leaf $newFile) argument (this additionally assumes that there's no input file whose name is the same as the output file's).
I need search then extract content from multiple txt files and then save each search result to separate txt files, e.g. serverabc.systeminfo.txt.
The below works however it returns all results to a single systeminfo.txt file that does not list what folder/file the results came from.
Ideally I need a single file output for each systeminfo extraction. In the extracted folder there are multiple folders i.e. abc123, def456 etc. Each file needs to search systeminfo file within for either "abc software" or "def software" then if found output that result to a single text file, i.e. abc123.systeminfo etc.
Once these files have been saved they will then be compared against a master.txt file for differences.
$path = "H:\Compare\Extracted"
$Text = "abc Software"
$Text2 = "def Software"
$Results = "H:\Compare\Results\$($file.name).txt"
$files = Get-ChildItem $path -recurse -Include *.txt
foreach ($file in $files) {
Get-Content $file |
Select-String -Pattern $Text, $Text2 |
select line |
Out-File $Results -Append
}
The variable $file isn't known outside (and particularly before) the foreach loop. Put the definition of $Results inside the loop and the code should work. Also, I think you should add -Expand to the select statement to expand the Line property.
foreach ($file in $files) {
$Results = "H:\Compare\Results\$($file.Directory.Name + '.' + $file.Name).txt"
Get-Content $file |
Select-String -Pattern $Text, $Text2 |
select -Expand Line |
Out-File $Results -Append
}
Use $file.BaseName instead of $file.Name if you want the output file created without including the extension of the source file.