Writing console output to a file - file is unexpectedly empty - powershell

I'm new to scripting and I am trying to write the information returned about a VM to a text file. My script looks like this:
Connect-VIServer -Server 192.168.255.255 -Protocol https -User xxxx -Password XXXXXX
Get-VM -Name xxxxxx
Get-VM xxxxx | Get-HardDisk | Select Parent, Name, Filename, DiskType, Persistence | FT -AutoSize
Out-File -FilePath C:Filepath
I am able to connect to the VM, retrieve the HDD info and see it in the console. The file is created where I want it and is correctly named. No data is ever put into the file. I have tried Tee-Object with the same results. I've also tried the -append switch. I did see a post about the data being returned as an array and Powershell is not able to move the data from an array to a string. Do I need to create a variable to hold the returned data and write to file from there?
Thanks

Guenther Schmitz' answer is effective, but it's worth explaining why:
Your Out-File -FilePath C:Filepath is a stand-alone command that receives no input.
An Out-File call with no input simply creates an empty file (0 bytes).
In order for cmdlets such as Out-File to receive input from (an)other command(s) (represented as ... below), you must use the pipeline, which means that you must place a | after the input command(s) and follow it with your Out-File call:Note that I'm using the shorter -Path parameter alias for the less commonly used -FilePath[1]
... | Out-File -Path C:Filepath
In the simplest case, as above, the entire command (pipeline) is placed on the same line; if you want to spread it across multiple lines for readability, you have have two choices:
Put a line break immediately after |, which tells PowerShell that the command continues on the next line:
... |
Out-File -Path C:Filepath
End a line with an explicit line continuation, which means placing ` at the very end of a line:
... `
| Out-File -Path C:Filepath
Alternatively, since you're using Out-File with its default behavior, you could use >, an output redirection, instead:
... > C:Filepath
A couple of asides:
Using Out-File with something other than strings, and using Format-* cmdlets in general, means that the output is only suitable for display (human consumption), not for further programmatic processing.
If you want to send output to both the console and a file, use the Tee-Object cmdlet, as TobyU suggests:
... | Tee-Object -Path C:Filepath
[1] Strictly speaking, -LiteralPath is the best choice in this case, because -Path interprets its arguments as wildcard expressions. However, omitting -Path, i.e. specifying the file path as a positional argument, as is common, implicitly binds to -Path.

try this:
Get-VM xxxxx |
Get-HardDisk |
Select Parent, Name, Filename, DiskType, Persistence |
Out-File -FilePath C:\Filepath

Related

Ignore wildcard characters [] in Powershell strings

I've got a Powershell script that takes a filename as input.
param([string]$folder, [string]$input_filename)
$logfile=$folder + "\duplicate.log"
Write-Output "`nScript: `n$transformation_filename`nLogfile: " | Out-file $logfile -Append
This works fine for most filenames, but it breaks down when the filename or folder name contains [].
dupfile.ps1 "C:\foldername [w]" "filename.xml"
Line 3 throws this error:
Cannot perform operation because the wildcard path C:foldername [w]\duplicate.log did not resolve to a file.
So the variables have to be treated more literally.
$logfile='$folder' + "\duplicate.log"
Nope, that's too literal.
$logfile="$folder" + "\duplicate.log"
Nope, same error.
How can I tell Powershell to insert the variable value but don't try to interpret anything in that value as wildcards?
Edit: I'm using Powershell 5.
My dupfile.ps1 contains only the 3 lines above.
Using this from the PS shell to run the script:
C:_SCRIPTS_\dupfile.ps1 "C:\foldername [w]" "test.txt"
One more addition, if I may. I have a few lines that redirect their error messages to the logfile.
Copy-Item -LiteralPath $inputPath -Destination $outputPath 2>&1|%{ "$_" } >>$logfile
The redirection trips over the [] again. I don't think I can use -LiteralPath here.
tl;dr
To ensure that file-path argument $var in redirection operations > $var / >> $var is treated literally if it contains [ and ], use
| Out-File -LiteralPath $var / | Out-File -LiteralPath $var -Append instead.
Copy-Item -LiteralPath $inputPath -Destination $outputPath 2>&1 |
ForEach-Object { "$_" } |
Out-File -LiteralPath $logfile # literal alternative to >> $logfile
As in your case, you may have to combine it with a redirection in order to ensure that additional streams beside the success output stream are targeted:
# Literal equivalent of *>> $logfile
... *>&1 | Out-File -LiteralPath $logfile -Append
Unfortunately, it gets tricky with capturing a given other stream only, as you then need to use the appropriate -*Variable common parameter, such as the common -ErrorVariable parameter:
# Literal equivalent of 2>> $logfile
... -ErrorVariable errs
$errs | Out-File -LiteralPath $logfile -Append
Caveat:
It is tempting to try to bypass the above solutions by escaping the literal path so that when it is interpreted as a wildcard, it is treated literally, using [WildcardPattern]::Escape().
However, this does not work as expected as of PowerShell 7.2.6, because the escaped form of the literal path is then used as the literal file path - see GitHub issue #9475 and also the discussion about whether > / >>, ... should treat their arguments as wildcards to begin with.
# !! Arguably SHOULD work, but doesn't as of 7.2.6:
# The file that is created is LITERALLY named for the ESCAPED form:
# `[1`].txt
'hi' > ([WildcardPattern]::Escape('[1].txt'))
Background information:
The -Path (-FilePath) parameter of PowerShell's provider cmdlets expects wildcard expressions for targeting file-system items by a name or path pattern. This parameter is implicitly targeted by the first positional argument, i.e. if you don't explicitly name the target parameter - e.g., Get-ChildItem foo is implicitly the same as Get-ChildItem -Path foo.
Surprisingly, this doesn't just apply to file-reading cmdlets (e.g., Get-ChildItem, Get-Content), but also to file-writing cmdlets (e.g., Set-Content, Out-File).
Note that the parameter named -Path is called -FilePath in Out-File for historical reasons. In PowerShell (Core) 7+, -Path was introduced as an alias name, for consistency.
Arguably, in file-writing/creating cmdlets this behavior is inappropriate - given that you usually know exactly what literal path you want to target - and there are several GitHub issues discussing this; an overview of all relevant discussions can be found in this comment on GitHub issue #17106.
In order for such arguments to be treated literally (verbatim), you must
use the -LiteralPath parameter instead.
In effect, > $var and >> $var are aliases for | Out-File -FilePath $var and | Out-File -FilePath $var -Append, which implies that $var is interpreted as a wildcard.
Using explicit Out-File calls with -LiteralPath, as shown at the top, avoids this problem.

Remove blank space from text file

I have a script to import the names of the virtual machines from hyper-v to a txt file.
The problem is that sometimes the names have blank spaces on the back, and when I try to turn them off or on with the script it does not find those machines because of the blank spaces.
Any way to remove the blank spaces from the file?
$vm = Get-VM | select name | Out-File -FilePath $ListVM
(Get-Content $ListVM | Select-Object -Skip 3) | ? {$_.trim() -ne ""} | Set-Content $ListVM
Withstanding Santiago's relevant alternate comment/approach. the cause of your issue may be:
Generally the Out-* cmdlets will force objects through PowerShell's for-display formatting system. You can use Set-Content from the start to avoid this, however you cannot simply select the name property doing so will create objects with the single property named "name". When Set-Content sees that it will write hash/object syntax to the file trying to represent the object, like:
#{Name=MachineName}
To avoid this simply expand the property you want to store in the file:
Get-VM |
Select-Object -ExpandProperty Name |
Set-Content -FilePath $ListVM
Or:
(Get-VM).Name | Set-Content -FilePath $ListVM
Note: you are assigning $VM to the output, however that will result in $VM being null. I'm not sure what the intent is, but if you want the resulting list stored in the variable add the -PassThru parameter like:
$vm = (Get-VM).Name | Set-Content -FilePath $ListVM -PassThru

Out-file , txt are created , no data saved [duplicate]

I'm new to scripting and I am trying to write the information returned about a VM to a text file. My script looks like this:
Connect-VIServer -Server 192.168.255.255 -Protocol https -User xxxx -Password XXXXXX
Get-VM -Name xxxxxx
Get-VM xxxxx | Get-HardDisk | Select Parent, Name, Filename, DiskType, Persistence | FT -AutoSize
Out-File -FilePath C:Filepath
I am able to connect to the VM, retrieve the HDD info and see it in the console. The file is created where I want it and is correctly named. No data is ever put into the file. I have tried Tee-Object with the same results. I've also tried the -append switch. I did see a post about the data being returned as an array and Powershell is not able to move the data from an array to a string. Do I need to create a variable to hold the returned data and write to file from there?
Thanks
Guenther Schmitz' answer is effective, but it's worth explaining why:
Your Out-File -FilePath C:Filepath is a stand-alone command that receives no input.
An Out-File call with no input simply creates an empty file (0 bytes).
In order for cmdlets such as Out-File to receive input from (an)other command(s) (represented as ... below), you must use the pipeline, which means that you must place a | after the input command(s) and follow it with your Out-File call:Note that I'm using the shorter -Path parameter alias for the less commonly used -FilePath[1]
... | Out-File -Path C:Filepath
In the simplest case, as above, the entire command (pipeline) is placed on the same line; if you want to spread it across multiple lines for readability, you have have two choices:
Put a line break immediately after |, which tells PowerShell that the command continues on the next line:
... |
Out-File -Path C:Filepath
End a line with an explicit line continuation, which means placing ` at the very end of a line:
... `
| Out-File -Path C:Filepath
Alternatively, since you're using Out-File with its default behavior, you could use >, an output redirection, instead:
... > C:Filepath
A couple of asides:
Using Out-File with something other than strings, and using Format-* cmdlets in general, means that the output is only suitable for display (human consumption), not for further programmatic processing.
If you want to send output to both the console and a file, use the Tee-Object cmdlet, as TobyU suggests:
... | Tee-Object -Path C:Filepath
[1] Strictly speaking, -LiteralPath is the best choice in this case, because -Path interprets its arguments as wildcard expressions. However, omitting -Path, i.e. specifying the file path as a positional argument, as is common, implicitly binds to -Path.
try this:
Get-VM xxxxx |
Get-HardDisk |
Select Parent, Name, Filename, DiskType, Persistence |
Out-File -FilePath C:\Filepath

Powershell Get-Content failing spuriously

I have a fairly simple PS script that was working perfectly, and now has suddenly started giving errors. I have narrowed the problem portion to a couple of Get-Content statements. Here's what the affected part of the script looks like:
$pathSource = "D:\FileDirectory"
Set-Location -Path $pathSource
Get-Content -Encoding UTF8 -Path FilesA*.txt | Out-File -Encoding ASCII FilesA_Digest.txt
Get-Content -Encoding UTF8 -Path FilesB*.txt | Out-File -Encoding ASCII FilesB_Digest.txt
This part of the script gathers up a collection of like-named files and concatenates them into a single text file for uploading to an FTP site. The Get-Content/Out-File was needed as the original files are encoded incorrectly for the FTP site. The script was working perfectly, running once each night for several weeks. Now, it gets the following error when the Get-Content statements are reached:
Get-Content : A parameter cannot be found that matches parameter name 'Encoding'.
At D:\FileDirectory\Script.ps1
Environment is Windows Server 2016. I've tried different variations on the Get-Content parameters, but nothing has worked. I know there is a bug that affects network-mapped drives, but that's not the case here -- all files are local.
Any ideas/suggestions?
The only plausible explanation I can think of is that a custom Get-Content command that lacks an -Encoding parameter is shadowing (overriding) the standard Get-Content cmdlet in the PowerShell session that's executing your script.
To demonstrate:
# Define a custom Get-Content command (function) that accepts only
# a (positional) -Path parameter, not also -Encoding.
function Get-Content { [CmdletBinding()] param([string] $Path) }
# Now try to use Get-Content -Encoding
Get-Content -Encoding Utf8 FilesA*.txt
You'll see the same error message as in your question.
Use Get-Command Get-Content -All to see all commands named Get-Content, with the effective command listed first.
Then examine where any custom commands may come from; e.g., your $PROFILE script may contain one.
To rule out $PROFILE as the culprit, start PowerShell without loading the profile script and examine Get-Content then:
powershell -noprofile # Windows PowerShell
pwsh -noprofile # PowerShell Core
A simple way to rule out custom overrides ad hoc is to call a command by its module-qualified name:
Microsoft.Powershell.Management\Get-Content ...
You can determine a built-in cmdlet's module name of origin as follows:
PS> (Get-Command Get-Content -All)[-1].ModuleName
Microsoft.PowerShell.Management
In a pinch you can also infer the originating module name from the URL of the help topic:
Googling Get-Content will take you to https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content - note how the cmdlet's module name, microsoft.powershell.management (case doesn't matter), is the penultimate (next to last) URI component.
It seems an issue with the out command. Can you please try below code :
$pathSource = "D:\FileDirectory"
Set-Location -Path $pathSource
Get-Content -Encoding UTF8 -Path FilesA*.txt | Set-Content -Encoding ASCII -path FilesA_Digest.txt
Get-Content -Encoding UTF8 -Path FilesB*.txt | Set-Content -Encoding ASCII -path FilesB_Digest.txt
Well, I don't know why it failed, but I can say that I have completely re-written the script and now it works. I have to note that, given the errors that were occurring, I also don't know why it is now working.
I am using the exact same calls to the Get-Content commandlet, with the -Encoding parameter and the pipe to Out-File with its own -Encoding parameter. I am doing the exact same actions as the previous version of the script. The only part that is significantly different is the portion that performs the FTP transfer of the processed files. I'm now using only PowerShell to perform the transfer rather than CuteFTP and it all seems to be working correctly.
Thanks to everyone who contributed.
Cheers
Norm
Not sure if it helps, but I was running into the same with:
$n = ni '[hi]' -value 'some text'
gc $n -Encoding Byte
$f = ls *hi*
$f.where{$_.name -eq '[hi]'}.Delete()
also looks like there's already a chain of SOs about this known bug see this answer

How to output script to file?

I found a ping script which is useful, but I'd prefer to write the output to a txt or csv rather than using Write-Host to output to the PS console.
What is the best way of doing this?
You will need to change the Write-Host to Out-File, or, better still, Out-Default. Using Out-Default will allow you to pipe the output to other cmdlets, and therefore allow you to handle the output differently on different occasions, depending on your particular need at the moment.
Write-Host bypasses the PowerShell pipeline, and effectively removes any objects it uses from the pipeline, making them unavailable for assignment or use by other cmdlets.
References:
Get-Help Write-Host
Get-Help Out-File
Get-Help Out-Default
Try adding the stdOut to an Array. Then write results at the end.
#Define the array
$myOutput = #()
#Do something here"
$myOutput += $myStdOut
#Done with something
$myOutput | out-file -FilePath c:\myOutput.txt -Encoding utf8 -NoClobber
$cat c:\myOutput.txt