How to insert one row to each file? - file

It's quite convenient to use Linux shell to append some content to a file, using pipe line operations and stream operations would do this.
But in PowerShell, the pipeline is used in object level, not in file level. Then, how can I for example, insert a row "helloworld" to a list of files, to become their first line?

It is not that trivial, but you can do this:
Get-ChildItem | foreach {$a = Get-Content $_
Set-Content $_ -Value "hi", $a}
Tho to be honest i do think it matches your definition of a pipeline.

Related

Create an array from get-child item list quicky in PowerShell

I am trying to shorten my scripts and reduce the number of foreach I use. I have a list of items using Get-ChildItem and I'd like to split each filename and create an array from the total list.
Example directory:
\\path\file1.name1.example1.jpg
\\path\file2.name2.example2.jpg
\\path\file3.name3.example3.jpg
Desired Outcome:
file1
file2
file3
Current Script:
#get-child item of all files in hte path
$files = get-childitem "\\Path\" -Recurse
$filenames = $files.Name.Split(".")[0]
Also tried:
#get-child item of all files in hte path
$files = get-childitem "\\Path\" -Recurse
$filenames = #($files.Name).Split(".")[0]
Of course, the above only produces file1 rather than part 0 of the split for each item in $files. Is there a way to get the desired outcome in a single pass without adding additional foreach actions to split each file one at a time?
You can apply most native operators to collections, so we can avoid explicit loops if we just rewrite your string manipulation routine to something that accomplished the same in a single operator call - like -replace for example:
$fileNames = (Get-ChildItem "\\Path\" -Recurse).Name -replace '\..*$'
The regular expression \..*$ will match the first literal . and remove it along with everything after.
While $collection -replace $pattern is generally faster than $collection |ForEach-Object {$_ -replace $pattern}, this might not actually be the most efficient approach - the grouping of Get-ChildItem ... in () means that we're waiting for Get-ChildItem to finish enumerating all files before we can start operating on the resulting array.
If you want the first result as fast as possible, take advantage of the pipeline and ForEach-Object:
Get-ChildItem -Path "\\Path\" -Recurse |ForEach-Object {
$_.Split('.')[0]
}
Unlike the foreach(...){} loop statement, the commands in a PowerShell pipeline execute consecutively, meaning that ForEach-Object can start operating on the first output item from Get-ChildItem long before Get-ChildItem is done.
My advice: Try both and measure the difference (either using Measure-Command, or something more fancy, like PSProfiler) - results may vary wildly, depending on your input and how you structure subsequent work on the resulting data

Compare-Object weird behaviour with processes

I want to compare two txt files with process information in PowerShell.
I looked up plenty of websites and there is always this simple
Compare-Object $(Get-Content [RandomFilePath]) $(Get-Content [RandomFilePath])
quote.
Now for some reason, whenever I try to do this with txt files that contain process information, the shell always outputs the whole content of both files, instead of the differences. However when I compare two txt files with random words in each line, the output correctly states the differences.
For some reason this only happens, when I compare two txt files, that contain process information.
Here's the code I used. I already changed directory beforehand, however I also tried it with the whole file path just in case but got the same result.
Compare-Object $(Get-Content proc.txt) $(Get-Content proc1.txt)
The content of both files is just a plain (with different processes running for each file)
Get-Process > proc.txt
I expect output like this:
InputObject SideIndicator
----------- -------------
System.Diagnostics.Process (EXCEL) =>
System.Diagnostics.Process (freecell) =>
System.Diagnostics.Process (notepad) =>
System.Diagnostics.Process (dexplore) <=
However what I get is exactly what gets written in a txt file if you enter
Get-Process > file.txt
Edit:
When I write it like this:
$a = Get-Process
notepad
$b = Get-Process
Compare-Object $a $b
I get:
System.Diagnostics.Process (notepad) =>
However, when I used txt files with the information from Get-Process in them instead of these variables, the console outputs the whole content of the file instead of the difference in them like the above.
The output redirection operator writes the default textual representation of the Get-Process output to a file. Meaning your output is text in tabular form, not Process objects. The tabular output contains information like CPU and memory usage, which vary over time, as well as PID and Handles, which change across invocations of a process. Hence it's pretty likely that most (if not all) of the lines in your input files are differing in at least one of these values. Meaning you simply don't have matching lines. Period.
What you actually seem to want to compare is just the process names. Of course you could parse those out of the text files, but I wouldn't recommend it. It's far better to fix your input:
Get-Process | Select-Object -Expand Name > proc.txt
Then you can compare 2 output files like this:
Compare-Object (Get-Content proc.txt) (Get-Content proc1.txt)
However, if you also require the other process information you may want to consider using Export-Clixml or Export-Csv instead:
Get-Process | Export-Clixml proc.xml
# or
Get-Process | Export-Csv proc.csv -NoType
Then you can compare 2 output files like this:
Compare-Object (Import-Clixml proc.xml) (Import-Clixml proc1.xml) -Property Name -PassThru
# or
Compare-Object (Import-Csv proc.csv) (Import-Csv proc1.csv) -Property Name -PassThru
Not sure what causes this but here's a troubleshooting tip.
Use following:
Compare-Object $(Get-Content proc.txt) $(Get-Content proc.txt)
There should be no difference now. Just to test if Compare works correctly using the process file.
Export processes to .csv file format and compare them again. that might work as well.

"Empty pipe not allowed" in foreach loop

I was asked to fix up the output of a PowerShell script a colleague wrote today, and noticed some strange behavior when trying to pipe output from a foreach loop. When I run the loop without piping the iterable object $gpos into the foreach as so:
# This is the foreach loop in question
foreach ( $gpo in $gpos ) {
[xml]$XML = Get-GPOReport -$gpo.DisplayName -ReportType Xml
$admins = $XML.DocumentElement.Computer.ExtensionData.Extention.RestrictedGroups.Member
# Not this one
$admins | foreach {
[PSCustomObject]#{
"GroupPolicy" = $gpo.DisplayName;
"Permisisons" = $_.name.'#text';
}
}
} | Export-CSV -Path \path\to\file.csv -NoTypeInformation
I get an error "An empty pipe element is not allowed".
However, if I pipe the $gpos object into the foreach loop like so:
$gpos | foreach {
$gpo = $_
# ...the rest of the code above
} | Export-CSV -Path \path\to\file.csv -NoTypeInformation
I am able to use the last pipe without issue. Why won't the pipe work when the statement starts with a foreach loop as opposed to piping in the iterable object? I rarely use the first format myself, so I've not run into this issue with code I write. I can't think of a functional reason both formats shouldn't work because if the piped input is null there is an appropriate exception which is thrown in this case.
Why won't the pipe work when the statement starts with a foreach loop as opposed to piping in the iterable object?
Because one syntax is the foreach statement, and the other is an alias for the ForEach-Object command. It's like the difference between Get-ChildItem and if {} else {}.
The PowerShell authors stupidly decided that overloading the term was a good idea. It's confused users of the language ever since.
Compare:
Get-Help about_Foreach -ShowWindow
Get-Help ForEach-Object -ShowWindow
The former even describes how PowerShell decides which is which:
When Foreach appears in a command pipeline, Windows PowerShell uses the foreach alias, which calls the ForEach-Object command. When you use the foreach alias in a command pipeline, you do not include the ($ in $) syntax as you do with the Foreach statement. This is because the prior command in the pipeline provides this information.
Bottom line is that the foreach will not send output down the pipeline. You can do this just fine:
$x = foreach ($i in 1..10) { $i }
But this will fail:
foreach ($i in 1..10) { $i } | Where-Object { $_ -eq 2 }
As Mathias R. Jessen notes in the comments, you can wrap the foreach statement in a subexpression to cause it to work with the pipeline:
$(foreach ($i in 1..10) { $i }) | Where-Object { $_ -eq 2 }
The ForEach-Object command always uses (and requires) the pipeline.
One is the language keyword foreach and the other is actually an alias to the cmdlet ForEach-Object.
A language keyword can't be a part of a pipeline which is why you get that exception. It's also why they mean different things in different contexts, the engine won't parse foreach as a keyword if it's already part of a pipeline.

Powershell loop files into program, if exit code is not equal to 0 move the file

I have a program that returns an exit code of 1 if a text file is empty or if it has a header record and no other contents.
I'm trying to have powershell loop the contents of a folder (text files) into the program, and if the program returns an exit code not equal to 0, move the file to another location. Here's what I have so far:
$files = Get-ChildItem -name C:\SourceFolder
$files | ForEach-Object {C:\TestProgram.cmd $_ } | ForEach-Object { if ($LASTEXITCODE -ne 0) {Move-Item $_.FullName C:\DestFolder} }
It's looping through the source folder fine and running the test program against it (dummy script returning an exit code of 1), and it looks like it's running the if statement correctly, but the $_.FullName after the Move-Item has lost its relationship with the original object (the file read in). It's now just the exit code at this point. I'm probably way off, is this even possible?
Thanks!
Totally possible, just don't split up your ForEach loops like you are. Your array of files are being fed into the first ForEach loop, but that ForEach loop doesn't actually pass anything down the pipeline, so the next ForEach loop has no input at all. Just combine the two of them:
$files | ForEach-Object {
C:\TestProgram.cmd $_
if ($LASTEXITCODE -ne 0) {Move-Item $_.FullName C:\DestFolder}
}

How to change read attribute for a list of files?

I am powershell newbie. I used a sample script and made substitute from get-item to get-content in the first line.
The modified script looks like below:
$file = get-content "c:\temp\test.txt"
if ($file.IsReadOnly -eq $true)
{
$file.IsReadOnly = $false
}
So in essence I am trying to action items contained in test.txt stored as UNC paths
\\testserver\testshare\doc1.doc
\\testserver2\testshare2\doc2.doc
When running script no errors are reported and no action is performed even on first entry.
Short answer:
sp (gc test.txt) IsReadOnly $false
Long answer below
Well, some things are wrong with this.
$file is actually a string[], containing the lines of your file. So the IsReadOnly property applies to the string[] and not to the actual files represented by those strings, which happen to be file names.
So, if I'm understanding you correctly you are trying to read a file, containing other file names, one on each line. And clear the read-only attribute on those files.
Starting with Get-Content isn't wrong here. We definitely are going to need it:
$filenames = Get-Content test.txt
Now we have a list of file names. To access the file's attributes we either need to convert those file names into actual FileInfo objects and operate on those. Or we pass the file names to a -Path argument of Set-ItemProperty.
I will take the first approach first and then get to the other one. So we have a bunch of file names and want FileInfo objects from them. This can be done with a foreach loop (since we need to do this for every file in the list):
$files = (foreach ($name in $filenames) { Get-Item $name })
You can then loop over the file names and set the IsReadOnly property on each of them:
foreach ($file in $files) {
$file.IsReadOnly = $false
}
This was the long and cumbersome variant. But one which probably suits people best with no prior experience to PowerShell. You can reduce the need for having multiple collections of things lying around by using the pipeline. The pipeline transports objects from one cmdlet to another and those objects still have types.
So by writing
Get-Content test.txt | Get-Item | ForEach-Object { $_.IsReadOnly = $false }
we're achieving exactly the same result. We read the contents of the file, getting a bunch of strings. Those are passed to Get-Item which happens to know what to do with pipeline input: It treats those objects as file paths; exactly what we need here. Get-Item then sends FileInfo objects further down the pipeline, at which point we are looping over them and setting the read-only property to false.
Now, that was shorter and, with a little practise, maybe even easier. But it's still far from ideal. As I said before, we can use Set-ItemProperty to set the read-only property on the files. And we can take advantage of the fact that Set-ItemProperty can take an array of strings as input for its -Path parameter.
$files = Get-Content test.txt
Set-ItemProperty -Path $files -Name IsReadOnly -Value $false
We are using a temporary variable here, since Set-ItemProperty won't accept incoming strings as values for -Path directly. But we can inline this temporary variable:
Set-ItemProperty -Path (Get-Content test.txt) -Name IsReadOnly -Value $false
The parentheses around the Get-Content call are needed to tell PowerShell that this is a single argument and should be evaluated first.
We can then take advantage of the fact that each of those parameters is used in the position where Set-ItemProperty expects it to be, so we can leave out the parameter names and stick just to the values:
Set-ItemProperty (Get-Content test.txt) IsReadOnly $false
And then we can shorten the cmdlet names to their default aliases:
sp (gc test.txt) IsReadOnly $false
We could actually write $false as 0 to save even more space, since 0 is converted to $false when used as a boolean value. But I think it suffices with shortening here.
Johannes has the scoop on the theory behind the problem you are running into. I just wanted to point out that if you happen to be using the PowerShell Community Extensions you can perform this by using the Set-Writable and Set-ReadOnly commands that are pipeline aware e.g.:
Get-Content "c:\temp\test.txt" | Set-Writable
or the short, aliased form:
gc "c:\temp\test.txt" | swr
The alias for Set-ReadOnly is sro. I use these commands weekly if not daily.

Resources