How can I pipe from Get-Content -Wait? - powershell

I would like to pipe the output of Get-Content $file -Wait to a custom PowerShell script. The script looks like this.
$lines = ($input | Out-String) -replace "`r", "" -split "`n"
foreach ($line in $lines) {
#Process $line
Write-Host $line
}
Basically the idea is to take the input, format it nicely and then process the output before it gets printed to the console.
The problem is nothing is getting sent to my script when I call it like cat $file -Wait | MyScript. If I do cat $file -Wait or cat $file | MyScript, everything works as expected. But combining the pipe and the wait parameter doesn't work.
Is there some syntax I need to use to allow processing the -Wait parameter? I tried using Out-String -Stream, but that doesn't work either.

The problem is with $input.
If you do this :
Get-Content $file -Wait | Get-Member -InputObject $input
Or
Get-Content $file -Wait | Get-Member -InputObject $_
You will get :
Get-Member : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
If Get-Member is unable to read the object going through the pipeline, you know that something is very wrong with the object (or the pipelining).
Let's try piping $input to Out-String, like you are doing in your script :
Get-Content $file -Wait | Out-String $input
You will get :
Out-String : A positional parameter cannot be found that accepts argument 'System.Collections.ArrayList+ArrayListEnumeratorSimple'.
At line:1 char:52
+ get-content '.\netstat anob.txt' -wait | Out-String <<<< $input
+ CategoryInfo : InvalidArgument: (:) [Out-String], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.OutStringCommand
So, indeed, "Get-Content" -Wait gives you a weird kind of object : a System.Collections.ArrayList+ArrayListEnumeratorSimple .
It looks like it's the result of the GetEnumerator() method from a System.Collections.ArrayList object, or something like that.
Given the fact that Get-Member or even "Get-Member -Force" is unable to read this kind of "Object", from Powershell's point of view, it's not a object.
The workaround would be to drop the -Wait parameter from Get-Content and find another way of achieving what you want, possibly by running Get-Content and then, running "Get-Content -Tail 1" several times in a loop.

This is possible if your script accepts pipeline input. You can see it as you have mentioned when you pipe to other cmdlets like Select-String. For example defining script.ps1 as:
process { Write-Host "line: $input" }
Then running
1..200 | foreach { add-content -Path test.txt -Value "$_"; start-sleep 1 }
in one PowerShell session and
gc test.txt -wait | .\script.ps1
in another, you can see that each line is piped to the script.

I don't see any way to do what you are asking. -Wait initiates a loop that never ends, the only way to stop is to manually kill it. Since it will always be stuck inside the loop anything you try to do after initiating the loop is never going to process.

The problem is in this line:
Write-Host $line
You should use Write-Output instead. Write-Output sends objects to pipeline, Write-Host directly to host (console).

Related

How can I send a node of xml to a script in Powershell?

I am making a powershell script that will be a shecdualed task on the server and that performs following things:
look in the export folder if there are export files older than 14 days and remove them ifso.
Calculated the date of yesterday and place it in a variable
Get a script, load it into a variable and adapt some arguments in it.
Extract a node out of the script into a variable and use it in the Cterm.exe application that starts afterwards.
Everything works, except the last step. The Cterm program starts, but asks a username and password. This is specified in the last variable with node of adapted script. It seems like he don't get that information and gets stuck. I want to send the content of the node to that last variable, but I get an error.
This is the error when I perform the code stepwise and another PowerShell session:
Method invocation failed because [System.String] does not contain a method named 'SelectSingleNode'.
At line:1 char:1
+ $inputscript = $tempfile.SelectSingleNode("//Text")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
This is the complete script:
#Place arguments in defined variables:
$filelocation="C:\temp" #E on the servers
$exportdestination="C:\temp"
$tempfile= ("D:\temp\temp-file.txt")
Write-Output $filelocation
$instanceName="GCS"
#Check if there are files (in the output folder) older then 14 days and remove them:
$limit = (Get-Date).AddDays(-15)
# Delete files older than the $limit.
$removedfile= Get-ChildItem -Path $exportdestination -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit }
try {
$removedfile | remove-Item -Force
}
catch {
{Write-Output "error: Not all the files could be removed!"}
}
#Export the file with the data of yesterday using the RAT script:
$yesterday=(Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
write-output "hello world"
$script= [xml](Get-Content C:\CTWS_export_infinity_v1.6.04.dms)
write-output "hello world"
$script -Replace ':startDate', $yesterday -Replace ':endDate', $yesterday -Replace ':filename', ($exportdestination+"\"+$instanceName)| Out-File -FilePath $tempfile
write-output "hello world"
#| Set-Content C:\CTWS_export_infinity_v1.6.04_$yesterday.dms
#$scripttemp = C:\CTWS_export_infinity_v1.6.04_$yesterday.dms
#Start-Process -FilePath "D:\CobasInfinity\HealthShare\bin\CTerm.exe" -ArgumentList #("/console=cn_ap:$instanceName $filelocation\temp-file.txt _SYSTEM INFINITY") -wait
$inputscript = $tempfile.SelectSingleNode("//Text")
& "D:\CobasInfinity\HealthShare\bin\CTerm.exe" /console=cn_ap:$instanceName $inputscript
#Remove-Item -Path $tempfile
How can I solve that final error?
I tried to save the content as an xml in the variable,
I tried to select one node of that variable,
I used 'hello world' prints to follow the progress of the script,
I tried to check the script by doing it step by step in another PowerShell session to see where to problem is.
How can I solve that final error?

Desired State Configuration can't get hash table from Script resource Get block

I have tested this using the built in Script resource from 1.1 as well as xScript 5.1.0.0 and get the same results. My Set and Test blocks work fine. I'm using several other script resources that are very similar and they work fine as well for the get block.
I've tried a lot of variations in syntax but it always comes back the same. I know the block is running because I commented out the line where the file which gets created gets removed and I see the file. I also ran this as a function in powershell and piped the output to Get-Member and can see it is indeed a hastable that is returned.
On a side note, I really don't like the method I'm using here to manage this setting through DSC. I'm open to other ideas as long as it is still within DSC.
Script StorePasswordsUsingReversibleEncyption
{
SetScript = {
secedit /export /cfg c:\temp\secpol.cfg
(gc C:\temp\secpol.cfg).replace("ClearTextPassword = 1", "ClearTextPassword = 0") | Out-File C:\temp\secpol.cfg
secedit /configure /db c:\windows\security\local.sdb /cfg c:\temp\secpol.cfg /areas SECURITYPOLICY /quiet
rm -force c:\temp\secpol.cfg -confirm:$false
}
TestScript = {
secedit /export /cfg c:\temp\secpol.cfg
$str = (Get-Content 'c:\temp\secpol.cfg' | select-String 'ClearTextPassword' -SimpleMatch).ToString()
rm -force c:\temp\secpol.cfg -confirm:$false
if ($str -eq 'ClearTextPassword = 0') {return $true}
else {return $false}
}
# Not working yet
GetScript = {
secedit /export /cfg c:\temp\secpol.cfg
$str = (Get-Content 'c:\temp\secpol.cfg' | select-String 'ClearTextPassword' -SimpleMatch).ToString()
rm -force c:\temp\secpol.cfg -confirm:$false
return #{Result = $str}
}
}
After I run Get-DSCConfiguration, the output returned in console is this:
Get-DscConfiguration : PowerShell DSC resource MSFT_ScriptResource failed to execute Get-TargetResource functionality
with error message: Failure to get the results from the script in a hash table format.
At line:1 char:1
+ Get-DscConfiguration
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (MSFT_DSCLocalConfigurationManager:root/Microsoft/...gurationManager)
[Get-DscConfiguration], CimException
+ FullyQualifiedErrorId : ProviderOperationExecutionFailure,Get-DscConfiguration
Try this:
GetScript = {
$null = secedit /export /cfg c:\temp\secpol.cfg
$str = (Get-Content 'c:\temp\secpol.cfg' | select-String 'ClearTextPassword' -SimpleMatch).ToString()
rm -force c:\temp\secpol.cfg -confirm:$false
return #{Result = $str}
}
The problem is that when you call an external command (like secedit), everything it writes to stdout is returned as output if this command (and that's pretty natural). But if don't catch it into a variable, it will be passed further to the output of your script block. The return statement is also a little misleading - it doesn't mean "return this thing only", but "write this thing to output stream, then return".
This means that your original GetScript doesn't return a single hashtable, but rather an arraythat looks like this:
#(
"some-output-from-secedit",
#{ Result = $str }
)
Assigning the output from external commands to a variable (I used $null in this case to denote I want to discard it) will prevent it from cluttering your script block's output.
Another way would be to redirect command's output to Write-Verbose (if you're interested in reading it) or to $null (if you don't care):
secedit /export /cfg c:\temp\secpol.cfg | write-verbose
can you try modifying your getscript block like this:
GetScript = {
start-process secedit -ArgumentList '/export /cfg c:\temp\secpol.cfg' -Wait
$str = (Get-Content 'c:\temp\secpol.cfg' | select-String 'ClearTextPassword' -SimpleMatch).ToString()
rm -force c:\temp\secpol.cfg -confirm:$false
return #{Result = $str}
}

Please explain, function to add string to first line of a file

Would you like to explain what is happing in the PowerShell code at the bottom of this post?
I got my first, lets say "hello world" in PowerShell, and it needed these lines of code. It works like a charm, but I am not sure what it does, exactly.
The questions starts at
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
So this is what I understand so far.
We create a function called Insert-Content. With the params (input that will be interpeted as a string and will be added to $path).
function Insert-Content {
param ( [String]$Path )
This is what the function does/processes:
process {
$( ,$_;
I am not sure what this does, but I guess it gets "the input" (the "Hello World" before the | in "Hello World" | Insert-Content test.txt).
And then we got -ea SilentylyContinue, but what does it do?
process {
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
It would be greatly appreciated if you could explain these two parts
$( ,$_;
-ea SilentylyContinue
Code needed/used: Add a string to the first line of a doc.
function Insert-Content {
param ( [String]$Path )
process {
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
}
}
"Hello World" | Insert-Content test.txt
process {...} is used for applying the code inside the scriptblock (the {...}) to each parameter argument that the function reads from a pipeline.
$_ is an automatic variable containing the current object. The comma operator , preceding the $_ converts the string value to a string array with a single element. It's not required, though. The code would work just as well with just $_ instead of ,$_.
Get-Content $Path reads the content of the file $Path and echoes it as to the success output stream as an array of strings (each line as a separate string).
The ; separates the two statements ,$_ and Get-Content $Path from each other.
| Out-File $Path writes the output back to the file $Path.
The subexpression operator $() is required to decouple reading the file from writing to it. You can't write to a file when a process is already reading from it, so the subexpression ensures that reading is completed before writing starts.
Basically this whole construct
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
echoes the input from the pipeline (i.e. the "Hello World") followed by the current content of the file $Path (effectively prepending the input string to the file content) and writes everything back to the file.
The -ea SilentlyContinue (or -ErrorAction SilentlyContinue) suppresses the error that would be thrown when $Path doesn't already exist.
The relevant section of code must be handled as a whole:
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
First, as others have said, -ea is the shortened version of -ErrorAction. -ErrorAction SilentlyContinue tells the cmdlet "Suppress any error messages and continue executing." See Get-Help about_Common_Parameters -ShowWindow.
Next, the $() is the sub-expression operator. It means "Evaluate what is between the parentheses as its own command and return the result(s)." See Get-Help about_Operators -ShowWindow.
This subexpression here is:
,$_;Get-Content $Path -ea SilentlyContinue
It contains two statements: ,$_ and Get-Content $Path -ea SilentlyContinue. The semicolon is just the end of statement identifier to separate the two.
,$_; is two kind of complex parts.
$_ is the special pipeline variable. It always contains whatever object is in the current pipeline. See Get-Help about_Automatic_Variables -ShowWindow for more about $_ (it's mostly used with ForEach-Object and Where-Object cmdlets, so check those out, too), and Get-Help about_pipelines -ShowWindow for more help with pipelines.
The comma here is the comma operator (see Get-Help about_Operators -ShowWindow again). It creates an array from the objects on either side. For example, 1,2,3 creates an array with three elements 1, 2, and 3. If you want a two item array, you can say 1,2.
What if you want a one item array? Well, you can't say 1, because Powershell will think you forgot something. Instead, you can say ,1.
You can test it with the -is operator:
PS C:\> 1,2,3 -is [Array]
True
PS C:\> 1 -is [Array]
False
PS C:\> ,1 -is [Array]
True
Why might you want to create a one item array? Well, if later on your code is assuming the item is an array, it can be useful. In early editions of Powershell, properties like .Count would be missing for single items.
For completeness, yes, I believe you could write:
$( #($_);Get-Content $Path -ea SilentlyContinue)
And I think you could rewrite this function:
function Insert-Content {
param ( [String]$Path )
process {
#Read from pipeline
$strings = #($_);
#Add content of specified file to the same array
$strings += Get-Content $Path -ea SilentlyContinue;
#Write the whole array to the file specified at $Path
$strings | Out-File $Path;
}
}
So this adds content from the pipeline to the start of a file specified by -Path.
It's also somewhat poor practice not to create a parameter for the pipeline object itself and define it. See... well, see all the topics under Get-Help "about_Functions*", but mostly the Advanced ones. This is an advanced topic.

Foreach loop in Powershell on the console

Very simple question here, I want to see how can we process a bunch of commands using foreach on the command line (not through a PS1 script).
For instance, I display the directory listing on the console, now I want to execute 2 commands per object.
Get-ChildItem | ForEach-Object { Write-Host $_ $_}
This is ok, it shows the filename twice, but lets say I wanted to run 2 Write-Host commands for the same object, would that be possible on the console?
PS: I'm trying to achieve writing an output to 2 files using the out-file cmdlet, so I can read something and have 2 separate out-file calls per object
Thanks
you can script in the console windows just as you would in a powershell file. Use the "`" (backtick) key to separate lines. e.g.:
PS > Write-Host `
>>> hello, world!
So you could do
PS > Get-ChildItem | ForEach-Object { `
>>> Write-Host $_ `
>>> doFoo() `
>>> doBar() `
>>> ` }
Basically you want to execute 2 commands in ForEach-Object statement, right?
Just use ; to separate commands in this way ForEach-Object { command1; command2 }
In your code it should be something like this
Get-ChildItem | ForEach-Object { Write-Host $_; Write-Host $_ }

How to pipe all output of .exe execution in Powershell?

In Powershell I am running psftp.exe which is PuTTy's homepage. I am doing this:
$cmd = "psftp.exe"
$args = '"username#ssh"#ftp.domain.com -b psftp.txt';
$output = & $cmd $args
This works; and I am printing out $output. But it only catches some output in that variable (like "Remote working directory is [...]") and is throwing other output to an error type like this:
psftp.exe : Using username "username#ssh".
At C:\full_script.ps1:37 char:20
+ $output = & <<<< $cmd $args
+ CategoryInfo : NotSpecified: (Using username "username#ssh".:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
This "Using username ..." etc looks like a normal FTP message. How can I make sure all output gets put into $output?
The problem is some output is being sent to STDERR and redirection works differently in PowerShell than in CMD.EXE.
How to redirect output of console program to a file in PowerShell has a good description of the problem and a clever workaround.
Basically, call CMD with your executable as a parameter. Like this:
UPDATE
I fixed my code so it would actually work. :)
$args = '"username#ssh"#ftp.domain.com -b psftp.txt';
$output = cmd /c psftp.exe $args 2`>`&1
Give this a try
$output = [string] (& psftp.exe 'username#ssh#ftp.domain.com' -b psftp.txt 2>&1)
There is a PowerShell bug about 2>&1 making error records. The [string] cast works around it.
& "my.exe" | Out-Null #go nowhere
& "my.exe" | Out-Default # go to default destination (e.g. console)
& "my.exe" | Out-String # return a string
the piping will return it in real-time
& "my.exe" | %{
if ($_ -match 'OK')
{ Write-Host $_ -f Green }
else if ($_ -match 'FAIL|ERROR')
{ Write-Host $_ -f Red }
else
{ Write-Host $_ }
}
Note:
If the executed program returns anything other than a 0 exitcode, the piping will not work. You can force it to pipe with redirection operators such as 2>&1
& "my.exe" 2>&1 | Out-String
sources:
https://stackoverflow.com/a/7272390/254276
https://social.technet.microsoft.com/forums/windowsserver/en-US/b6691fba-0e92-4e9d-aec2-47f3d5a17419/start-process-and-redirect-output-to-powershell-window