comma seperate 2 values in powershell - powershell

I am listing files in a directory along with their epoch time (being used on a Linux box, for centralized file management).
I'm having issues getting it to join 2 fields with a comma in between.
folder structure is
c:\temp\test
test1.txt
test2.txt
Expected output:
File: test1.txt,1518167449
File: test2.txt,1518167449
i have all the information in:
(Get-ChildItem C:\temp\test | select name, #{name='lastwritetime';Expression={[int][double]::Parse((Get-Date $_.LastWriteTime -UFormat %s))}})
gives the output
Name lastwritetime
test1.txt 1518167449
test2.txt 1518167455
but i'm not able to join them so they look in the correct format (with the comma join).

You could do something like this:
$Files = (Get-ChildItem C:\temp\test | select name, #{name='lastwritetime';Expression={[int][double]::Parse((Get-Date $_.LastWriteTime -UFormat %s))}})
$Files | ForEach-Object {
"$($_.Name),$($_.LastWriteTime)"
}
This uses a ForEach-Object loop that iterates through each file and outputs a string. The string uses subexpressions $( ) to allow us to access the two properties we want and interpolate them in the string with a comma between them.
Note that you don't have to use a $Files variable, you could instead chain the ForEach-Object command on the end of your Select, but because that command is already quite long this makes the code a bit cleaner.
Another option is this:
$Files | ForEach-Object {
"{0},{1}" -f $_.Name,$_.LastWriteTime
}
Its up to you as to which you feel is more readable.
You could also consider using the ConvertTo-CSV cmdlet, although that will also add quote marks around each value. If your ultimate destination is CSV though using that or Export-CSV is cleaner/simpler.

Related

Nested pipeline variable in powershell

I have the following directory tree composed of a root directory containing 10 subdirectories, and 1 file in each subdirectory.
root/
dir1/
file
dir2/
file
...
dir10/
file
I would like to edit the content of the files recursively, replacing a string "str1" by "str2". I issued the following command in powershell:
Get-ChildItem -Directory | foreach {(get-content $_/file) -replace "str1", "str2" | set-content $_/file}
And it worked like a charm, but I still do not understand how. I use a pipeline in the foreach loop, but the following call to $_ still refers to the pipeline outside the foreach loop. Why is it so?
I don't think your command did work, is the problem.
The -Directory switch of Get-ChildItem makes it only return directories, not files. If you want to return files, use the -File switch.
Next up, if you have a list of items from Get-ChildItem, those give you a System.IO.FileSystemInfo object. We can provide those directly to the Get-Content command to read the file into a string.
From a string, you can call any of the general operators PowerShell offers, including the string replace operator, -Replace. The output of this can be piped over to Set-Content and used to update or append content to an existing file.
Get-ChildItem | foreach {
(get-content $_) -replace "str1", "str2" | Set-Content $_.FullName
}
Note the only real change here is that I removed -Directory from Get-ChildItem, and then fixed the syntax on the $PSItem (the official name for the present variable in a forEach loop, often written as $_).
The reason you can use the syntax I showed is that forEach-object gives you that special $_ or $PSitem variable to use to reference $this in a collection.
The special variable $_ is not unique for the foreach loop.
It is a placeholder for the object being passed through the pipe line.
This article: POWERSHELL POWER LESSON: THIS IS NO ORDINARY VARIABLE goes into more detail about the $_ variable.
Both $_ and the | are inside the foreach loop in the curly braces { } or script block.

Use Powershell to compare two text files and remove lines with duplicate

I have two text files that contain many duplicate lines. I would like to run a powershell statement that will output a new file with only the values NOT already in the first file. Below is an example of two files.
File1.txt
-----------
Alpha
Bravo
Charlie
File2.txt
-----------
Alpha
Echo
Foxtrot
In this case, only Echo and Foxtrot are not in the first file. So these would be the desired results.
OutputFile.txt
------------
Echo
Foxtrot
I reviewed the below link which is similar to what I want, but this does not write the results to an output file.
Remove lines from file1 that exist in file2 in Powershell
Here's one way to do it:
# Get unique values from first file
$uniqueFile1 = (Get-Content -Path .\File1.txt) | Sort-Object -Unique
# Get lines in second file that aren't in first and save to a file
Get-Content -Path .\File2.txt | Where-Object { $uniqueFile1 -notcontains $_ } | Out-File .\OutputFile.txt
Using the approach in the referenced link will work however, for every line in the original file, it will trigger the second file to be read from disk. This could be painful depending on the size of your files. I think the following approach would meet your needs.
$file1 = Get-Content .\File1.txt
$file2 = Get-Content .\File2.txt
$compareParams = #{
ReferenceObject = $file1
DifferenceObject = $file2
}
Compare-Object #compareParams |
Where-Object -Property SideIndicator -eq '=>' |
Select-Object -ExpandProperty InputObject |
Out-File -FilePath .\OutputFile.txt
This code does the following:
Reads each file into a separate variable
Creates a hashtable for the parameters of Compare-Object (see about_Splatting for more information)
Compares the two files in memory and passes the results to Out-File
Writes the contents of the pipeline to "OutputFile.txt"
If you are comfortable with the overall flow of this, and are only using this in one-off situations, the whole thing can be compressed into a one-liner.
(Compare-Object (gc .\File1.txt) (gc .\File2.txt) | ? SideIndicator -eq '=>').InputObject | Out-File .\OutputFile.txt

Combine TXT files in a directory to one file with column added at end with for file name

I have got a set of txt files in a directory that I want to merge together.
The contents of all the txt files are in the same format as follows:
IPAddress Description DNSDomain
--------- ----------- ---------
{192.168.1.2} Microsoft Hyper-V Network Adapter
{192.168.1.30} Microsoft Hyper-V Network Adapter #2
I have the below code that combines all the txt files in to one txt file called all.txt.
copy *.txt all.txt
From this all.txt I can't see what lines came from what txt file. Any ideas on any bits of code that would add an extra column to the end txt file with the file name the rows come from?
As per the comments above, you've put the output of Format-Table into a text file. Note that Format-Table might be visually structured on screen, but is just lines of text. By doing that you have made it harder to work with the data.
If you just want a few properties from the results of the Get-WMIObject cmdlet, use Select-Object which (in the use given here) will effectively filter the data for just the properties you want.
Instead of writing text to a simple file, you can preserve the tabular nature of the data by writing to a structured file (i.e. CSV):
Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE -ComputerName SERVERNAMEHERE |
Select-Object PSComputerName, IPAddress, Description, DNSDomain |
Export-Csv 'C:\temp\server.csv'
Note that we were able to include the PScomputerName property in each line of data, effectively giving you the extra column of data you wanted.
So much for getting the raw data. One way you could read in all the CSV data and write it out again might look like this:
Get-ChildItem *.csv -Exclude all.csv |
Foreach-Object {Import-Csv $_} |
Export-Csv all.csv
Note that we exclude the output file in the initial cmdlet to avoid reading and writing form/to the same file endlessly.
If you don't have the luxury to collect the data again you'll need to spool the files together. Spooling files together is done with Get-Content, something like this:
Get-ChildItem *.txt -Exclude all.txt |
Foreach-Object {Get-Content $_ -Raw} |
Out-File all.txt
In your case, you wanted to suffix each line, which tricker as you need to process the files line-by-line:
$files = Get-ChildItem *.txt
foreach($file in $files) {
$lines = Get-Content $file
foreach($line in $lines) {
"$line $($file.Name)" | Out-File all.txt -Append
}
}

Delete duplicate files with Powershell except the file specified

I am using the following code to delete duplicate files in one folder:
ls *.wav -recurse | get-filehash | group -property hash | where { $_.count -gt 1 } | % { $_.group | select -skip 1 } | del
I have two issues. I want to limit this to only one filehash at a time and I need to specify the file name I want to keep.
As an example, I have a folder named Recordings. The first five files listed all have the same filehash but only one has the filename that has already been entered in my database.
Recordings
It would be great if I could use the -Exclude parameter for the del cmdlet but that parameter does not accept pipeline input.
I also considered using the code above and then renaming the remaining file afterward but the code is not limited to one filehash.
It all depends on how you want it to work. For example, if you know the file name you want to keep in advance, you could do it this way:
$fileName = 'file1.txt'
$fileHash = Get-FileHash .\$filename
$duplicates = ls -Recurse | Get-FileHash | Where-Object {$_.Hash -eq $fileHash.Hash -and ($_.Path | Split-Path -Leaf) -ne $fileName }
$duplicates | del
This sequence sets the filename, gets the hash of that file, and then the main command checks for other files with that same hash that doesn't have the same filename.
Note: Test first to make sure this will do what you expect before you execute the del command.
Update: It appears that Get-FileHash puts some sort of lock on the files being hashed so you cannot immediately pipe to the del (Remove-Item) command. I modified the results to save the array of duplicates to a variable and then pass that to the delete command which works.

Parse and Switch Elements of Folder Names with Powershell 2.0

I have been trying to write a powershell script (my first) to
parse out only the folders within a directory
select only those folders matching a specific naming convention ('SEASON YEAR')
switch the order of the elements of the name ('YEAR SEASON')
I eventually used the program BulkRenameUtility to do this using the regexp ^(\w+) (\d+) and switching the token order to $2 $1 -- however, I still am learning Powershell and would like to be able to do this without using an external program.
So, to re-iterate, at C:\Users\Test
there are folders and files.. some of the folders are named Spring 2015, Fall 2014, for example. However, other folders have names such as geogex. Files have names such as ENG1A SPRING 2015.odtand untitled_0.odt.
How do I only change the names of the folders named like "Spring 2015" to read "2015 Spring", "2014 Fall" etc. ?
I was able to use
gci | ? {$_.PSIsContainer} | select-string -pattern '\d+'
to accomplish 1 and 2 but am stuck on using this to do part 3: actually rename by reversing the elements of the name. I tried putting the above within a variable and like so:
gci | ? {$_.PSIsContainer} | select-string -pattern '\d+' | %{$data = $_.line; Write-Output "$data"};
however, while the above outputs exactly the folders I want the array $data seems to only hold the last line of output. Such that:
gci | ? {$_.PSIsContainer} | select-string -pattern '\d+' | %{$data = $_.line; Write-Output "$data"};
$data
will output:
test 123
test 321
test 321
I am unsure if this is even the a valid direction to begin with.
Any help would be greatly appreciated.
This should get the job done.
$Path = "C:\Users\Test"
$regex = "^(Spring|Summer|Fall|Winter)\s+\d{4}$"
Get-ChildItem $Path |
Where-Object {$_.PSIsContainer -and $_.Name -match $regex} |
Rename-Item -NewName {$split = $_.Name -split "\s+"; "{0} {1}" -f $split[1],$split[0]}
We use that regex to filter out the folder that fit your convention. Should be a little more targeted using specific season names. The year is a little more lacked by just looking for 4 numbers.
Other ways to do it but for the rename I just split the name on the space and reversed the output using the -f format operator.