Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows cmdlet for conditional out-file file creation

Get-Service AppHostSVC,FTPSVC,IISAdmin,MSFTPSVC,W3SVC,WAS,WMSVC |
    Select Name,Status,DisplayName|
    Where-Object {$_.Status -EQ "Stopped"}
    ConvertTo-Html |
    Out-File -FilePath c:\checker.html

In the above cmdlet, how can I add a conditional file creation? I need the file to be created only when the said service are in STOPPED status.

like image 758
sakworld Avatar asked Oct 27 '25 21:10

sakworld


1 Answers

To address the question as asked:

tl;dr

Courtesy of Santiago Squarzon's comments: Use Set-Content instead of Out-File, because Set-Content only creates its output file if it receives actual input:

# Create c:\checker.html only if any services are in the stopped state.
Get-Service AppHostSVC,FTPSVC,IISAdmin,MSFTPSVC,W3SVC,WAS,WMSVC |
    Select Name,Status,DisplayName|
    Where-Object {$_.Status -EQ "Stopped"}
    ConvertTo-Html |
    Set-Content -Encoding Unicode -FilePath c:\checker.html

Note:

  • -Encoding Unicode is meant for Windows PowerShell, to make Set-Content match Out-File's default character encoding there. In PowerShell (Core) 7+, this isn't necessary, because all cmdlets use the same encoding there, namely BOM-less UTF-8.

  • Since ConvertTo-Html outputs strings, Set-Content in effect produces the same file content as Out-File, but it wouldn't be if the input contained complex objects, such as the [pscustomobject] instances created by your Select (Select-Object) command. Read on for background information and for a solution.


The following are the general-purpose file-writing cmdlets that create output files conditionally, namely only if they receive actual input; it is unclear whether that is by deliberate design; given that the linked documentation doesn't discuss the behavior, it is somewhat risky to rely on this behavior:

  • Set-Content (and the closely related Add-Content)

  • Tee-Object

By contrast, Out-File and its effective alias > create output files unconditionally - whether they receive input or not. Ultimately, this makes for more predictable behavior and is in line with how > works in POSIX-compatible shells (such as Bash).

Other - special-purpose - file-writing cmdlets exhibit one or the other behavior; e.g., Export-Csv and Export-CliXml unconditionally create, where as Invoke-RestMethod -OutFile does not.

Note the pitfall associated with conditional output-file creation:

  • If no file is created by a particular call, any preexisting output file is retained, which could be mistaken for the results of the call.

  • To avoid this, delete any preexisting file first.


Set-Content alone is not a full substitute for Out-File, because the latter applies the rich for-display formatting to complex objects that you normally see in the console, whereas Set-Content performs simple .ToString() stringification.

Note that the rich formatting Out-File produces is meant for the human observer, not for programmatic processing; for the latter, use a structured text format, such as CSV.

A general solution therefore requires additional work:

Before offering solutions that rely on Set-Content / Tee-Object's behavior, consider simply deleting an unconditionally created output file after the fact if it is found to be empty (which also avoids the pitfall discussed above):

# Unconditionally create the output file.
$outFile = 'out.txt'
Get-ChildItem -File *.txt |
  Select-Object Name, Length |
  Out-File $outFile

# Remove the output file if it is empty (size of 0 bytes).
if ((Get-Item $outFile).Length -eq 0) { Remove-Item $outFile }

If you deliberately want to keep any preexisting file and if it is acceptable to collect all output in memory first:

# Collect all output, if any, first.
$results = 
  Get-ChildItem -File *.txt |
    Select-Object Name, Length

# Write the file only if there are results.
if ($results) { $results | Out-File out.txt }

If you do want to rely on the current conditional file-creation behavior of Set-Content and Tee-Object:

The simplest solution is to repurpose Tee-Object; using a simplified command:

# Only write to out.txt if at least one file matches.
Get-ChildItem -File *.txt |
  Select-Object Name, Length |
  Tee-Object -FilePath out.txt | Out-Null
  • Tee-Object applies the same output formatting as Out-File, and also uses the same default character encoding - but, as discussed, it only creates the output file if actual input is received.

  • Because Tee-Object's purpose is to also pass input through, in addition to writing to a file, Out-Null is used to suppress any output from it.

    • You may alternatively use $null = Get-ChildItem ... instead of appending | Out-Null

Alternatively, combine Out-String with Set-Content:

# Only write to out.txt if at least one file matches.
Get-ChildItem -File *.txt |
  Select-Object Name, Length |
  Out-String -Stream |
  Set-Content -Encoding Unicode out.txt

Note:

  • Out-String performs the same output formatting as Out-File. -Streammakes it emit the formatted strings _one by one_, allowingSet-Content` to write the results in a streaming fashion.
  • Alternatively, if you don't mind building the file content as a multi-line string in memory up front, omit -Stream and add -NoNewLine to the Set-Content call.
like image 94
mklement0 Avatar answered Oct 29 '25 20:10

mklement0



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!