Piping Powershell output to a text file - powershell

I have the following powershell script that renames files from one location to another with a sequential filename. Ultimately, these file changes need to be mapped, as in original - new. Currently, I just have a Write-Host cmdlet and I just copy the cmd windows output into a txt file then run through a python script I wrote to spit out the original and renamed files into an excel file. I was wondering if there was an easier way to do this in the initial ps script. Even something tab delimited would be easily copy-pasteable into an excel file.
Set-Location -Path "C:\Users\mmcintyre\Desktop\grail_new"
$destLoc = "C:\Users\mmcintyre\Desktop\renamed"
$countRef = [ref] 0
Get-ChildItem -Filter *.pdf -Recurse |
Copy-Item -WhatIf -Destination { '{0}\{1}.pdf' -f $destLoc,++$countRef.Value }
Any help would be greatly appreciated.
Edit: I am currently using PS 2.0.

The following outputting old-name/new-name pairs to a TSV file in addition to the copy operation (PSv3+ syntax):
$countRef = [ref] 0
Get-ChildItem -Filter *.pdf -Recurse | ForEach-Object {
$newFullName = '{0}\{1}.pdf' -f $destLoc, ++$countRef.Value
Copy-Item -WhatIf -LiteralPath $_.FullName -Destination $newFullName
[pscustomobject] #{
Old = $_.FullName
New = $newFullName
}
} | Export-Csv -Delimiter "`t" NameMappings.tsv
This creates a TSV (tab-separated values) file with columns named Old and New that contain the old and new full filenames, respectively.
PSv2: The [pscustomobject] #{ ... } syntactic sugar for creating custom objects from hashtables is not available in v2, so New-Object must be used:
$countRef = [ref] 0
Get-ChildItem -Filter *.pdf -Recurse | ForEach-Object {
$newFullName = '{0}\{1}.pdf' -f $destLoc, ++$countRef.Value
Copy-Item -WhatIf -LiteralPath $_.FullName -Destination $newFullName
New-Object PSCustomObject -Property #{
Old = $_.FullName
New = $newFullName
}
} | Export-Csv -Delimiter "`t" NameMappings.tsv
Caveat: -Property accepts a hashtable[1]
, which means that its key ordering is not guaranteed, so the ordering of properties of the resulting object will typically not reflect the input order - in this case it just happens to do so.
If the resulting property order is undesired, you have two options:
Slow, but convenient: insert a Select-Object call with the properties in the desired order (e.g., Select-Object Old, New).
More cumbersome: Construct the object empty at first New-Object PSCustomObject, and then attach properties one by one in the desired order with individual Add-Member calls.
[1] The PSv3+ [pscustomobject] #{ ... } syntax is seemingly also hashtable-based, but it is parsed in a way that preserves the key order; i.e., as if you had implicitly used [ordered] #{ ... }.

Related

remove duplicates while combining System.IO.FileInfo objects

I'm trying to combine System.IO.FileInfo objects (from distinct Get-ChildItem calls) together. I've found working solutions (i.e. using PowerShell array) from this question:
Combine the results of two distinct Get-ChildItem calls into single variable to do the same processing on them
$target = "C:\example"
$Files = #( Get-ChildItem -LiteralPath $target -Force -Attributes !D )
$Files += #( Get-ChildItem -LiteralPath $target -Force -Attributes !D ) # for demo. & simplicity, I'm using the same path here
$Files | Write-Host # here the same entries are duplicated
However, the same entries from the System.IO.FileInfo objects are duplicated in the resulting object. I'm wondering if there is an elegant way to combine the objects while removing the duplicates?
PS: Files are "duplicated" if they have the same ".FullName".
$Files = #($Files | Sort-Object -Property FullName -Unique)

Powershell search directory for code files with text matching input a txt file

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"
}
}

Get attributes for list of files

I am not a coder, but know enough to do some simple tasks. Using Powershell
I need to get:
folder/subfolder (s)/Filename.txt
Mode
LastWriteTime
Get-childItem and FullName work but file path too long..
I tried:
Get-ChildItem -Recurse -Force |foreach-object -process {$_.Fullname,LastWriteTime,Mode} >FullFileName.csv
and a number of other scripts I found online, Including this one
Get-ChildItem "MyFolderDirectory" | Select Name, #{ n = 'Folder'; e = { Convert-Path $_.PSParentPath } }, `
#{ n = 'Foldername'; e = { ($_.PSPath -split '[\\]')[-2] } }
I just cant get what I want.. this is what I need, there has to be an easy way to do this, but I just am not skilled enough to figure it out.
-a---- 9/9/2019 9:39AM folder1/folder2/Filename.txt
Does this help you?
Get-ChildItem -recurse | Foreach-Object { write-output "$($_.Mode) $($_.LastWriteTime) $($_.FullName)" }
This will grab the properties for each file or directory returned by Get-ChildItem
I would do something like this:
Get-ChildItem -Recurse -Force | Select-Object Mode, LastWriteTime, FullName
to get the list as array of objects. That way, it is also easy to export the results to a CSV file you can open in Excel for instance. To do that, simply append
| Export-Csv -Path 'X:\filelist.csv' -NoTypeInformation
to the above line. (change the X: to a existing drive on your machine of course)

Import .csv to create a list of filenames and corresponding owners

I am working on creating a script that will read a .csv document containing a single column of filenames (one per cell) and search a larger folder for each of the files matching the filenames provided and identify the 'owner' using:
(get-acl $file).owner
Currently I have several bits of code that can do individual parts, but I am having a hard time tying it all together. Ideally, a user can simply input file names into the .csv file, then run the script to output a second .csv or .txt identifying each file name and it's owner.
csv formatting will appear as below (ASINs is header):
ASINs
B01M8N1D83.MAIN.PC_410
B01M14G0JV.MAIN.PC_410
Pull file names without header:
$images = Get-Content \\path\ASINs.csv | Select -skip 1
Find images in larger folder to pull full filename/path (not working):
ForEach($image in $images) {
$images.FullName | ForEach-Object
{
$ASIN | Get-ChildItem -Path $serverPath -Filter *.jpg -Recurse -ErrorAction SilentlyContinue -Force | Set-Content \\path\FullNames.csv
}
}
At that point I would like to use the full file paths provided by FullNames.csv to pull the owners from the files in their native location using the above mentioned:
(get-acl $file).owner
Does anyone have any ideas how to tie these together into one fluid script?
EDIT
I was able to get the following to work without the loop, reading one of the filenames, but I need it to loop as there are multiple filenames.
New CSV Format:
BaseName
B01LVVLSCM.MAIN.PC_410
B01LVY65AN.MAIN.PC_410
B01MAXORH6.MAIN.PC_410
B01MTGEMEE.MAIN.PC_410
New Script:
$desktopPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop)
$images = $desktopPath + '\Get_Owner'
Get-ChildItem -Path $images | Select BaseName | Export-Csv $desktopPath`\Filenames.csv -NoTypeInformation
$serverPath = 'C:\Users\tuggleg\Desktop\Archive'
$files = Import-Csv -Path $desktopPath`\Filenames.csv
While($true) {
ForEach ($fileName in $files.BaseName)
{
Get-ChildItem -Path $serverPath -Filter "*$fileName*" -Recurse -ErrorAction 'SilentlyContinue' |
Select-Object -Property #{
Name='Owner'
Expression={(Get-Acl -Path $_.FullName).Owner}
},'*' |
Export-Csv -Path $desktopPath`\Owners.csv -NoTypeInformation
}
}
Any ideas on the loop issue? Thanks everyone!
This example assumes your csv contains partial filenames. It will search the filepath and filter for those partials.
Example.csv
"ASINs"
"B01M8N1D83.MAIN.PC_410"
"B01M14G0JV.MAIN.PC_410"
Code.ps1
$Files = Import-Csv -Path '.\Example.csv'
ForEach ($FileName in $Files.ASINs)
{
Get-ChildItem -Path $serverPath -Filter "*$FileName*" -Recurse -ErrorAction 'SilentlyContinue' |
Select-Object -Property #{
Name='Owner'
Expression={(Get-Acl -Path $_.FullName).Owner}
},'*' |
Export-Csv -Path '\\path\FullNames.csv' -NoTypeInformation
}

Scan txt file for multiple strings and save the following lines

I have a problem that I am trying to solve, however, due to my non existing PowerShell knowledge it is proving to be harder than I hoped. So any help would be appreciated.
The problem can be simplified as:
Find a string in a txtfile
Extract the information on the row after that string
Store the information in a handle
Find a second string in the txtfile and repeat the procedure
Store both strings in a new file or delete everything else in the txt file.
I am then trying to do this for approx 20k files. I would love to have the information under their keyword and comma delimited so that I can import them in other systems.
My files look somewhat like the following
random words
that are unimportant
Keyword
FirstlineofNumbersthatIwanttoExtract
random words again that are unimportant
Secondkeyword
SecondLineOfNumbersThatIWantToExtract
end of the file
All files are however not similar in terms of the row that the lines I want to extract are on. I would the output to be something like
Keyword, SecondKeyword
FirstLineOfNumbersThatIWantToExtract, SecondLineOfNumbersThatIWantToExtract
And done. I got this far
[System.IO.DirectoryInfo]$folder = 'C:\users\xx\Desktop\mappcent3'
foreach ($file in ($folder.EnumerateFiles())) {
if ($file.Extension -eq '.txt') {
$content = Get-Content $file
$FirstRegex = 'KeyWordOne
(.+)$'
$First_output = "\1"
$test = Select-String -Path $file.FullName -Pattern $FirstRegex
}
}
This would do something similar to what you are asking. This requires PowerShell 3.0+
$path = 'C:\users\xx\Desktop\mappcent3'
$firstKeyword = "Keyword"
$secondKeyword = "Secondkeyword"
$resultsPath = "C:\Temp\results.csv"
Get-ChildItem $path -Filter "*.txt" | ForEach-Object{
# Read the file in
$fileContents = Get-Content $_.FullName
# Find the first keyword data
$firstKeywordData = ($fileContents | Select-String -Pattern $firstKeyword -Context 0,1 -SimpleMatch).Context.PostContext[0]
# Find the second keyword data
$secondKeywordData = ($fileContents | Select-String -Pattern $secondKeyword -Context 0,1 -SimpleMatch).Context.PostContext[0]
# Create a new object with details gathered.
[pscustomobject][ordered]#{
File = $_.FullName
FirstKeywordData = $firstKeywordData
SecondKeywordData = $secondKeywordData
}
} | Export-CSV $resultsPath -NoTypeInformation
Select-String is what does most of the magic here. We take advantage of -Context which consumes lines before and after the match. We want the one following so that is why we use 0,1. Wrap that up in a custom object and then we can export it to a CSV file.
Keyword Overlap
Beware that your keywords can overlap and create odd results in your output files. In your sample Keyword matches multiple lines so the result set would reflect that.
If you did just want to write back to the original file you could easily do that as well
"$firstKeywordData,$secondKeywordData" | Set-Content $_.FullName
Or something similar.
The Select-String cmdlet has a -Context parameter that makes it easy to extract lines before or after the line on which there's a match.
You can use Export-Csv to export to the format you require (although with 20K files you may want to write directly to the output files)
foreach($file in Get-ChildItem C:\users\xx\Desktop\mappcent3 |Where {-not $_.PsIsContainer})
{
$FirstKeyword = 'FirstKeyword'
$FirstLine = Select-String -Path $file.FullName -Pattern $FirstKeyword -Context 0,1 |Select -Expand Context -First 1 |Select -Expand PostContext
$SecondKeyword = 'SecondKeyword'
$SecondLine = Select-String -Path $file.FullName -Pattern $SecondKeyword -Context 0,1 |Select -Expand Context -First 1 |Select -Expand PostContext
New-Object psobject -Property #{$FirstKeyword=$FirstLine;$SecondKeyword=$SecondLine} |Export-Csv (Join-Path $file.DirectoryName ($file.BaseName + '_keywords.txt'))
}