Home > Net >  Windows cmdlet for conditional out-file file creation
Windows cmdlet for conditional out-file file creation

Time:09-09

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.

CodePudding user response:

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; the linked documentation doesn't discuss the behavior:

By contrast, Out-File and its effective alias > create output files unconditionally - whether they receive input or not.

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:

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.

Finally, you may simply delete 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 }

CodePudding user response:

You should place all the services' names in a file or something. You can read the file using get-content

Then you can iterate each of the services names by using a foreach loop. For each of the services, you can check the service's status and based on that you can decide to write that in file or not.

Now note by, I have just given a simplified structure of what you were trying to achieve but since I am not sure whether you want the content to be appended in the same file or the file has to be overwritten or something similar; I just gave a simple statement so that you get the logic.

Further I'd like you to put try/catch block and capture the error because most of the services wont be readily available. So, it is always better to capture the error message or at least the exception message in the catch block.

Also note, in case of nothing returned, then you can just put a file creation statement in the else block which will help you. It means that service is not stopped hence the file is being created as empty or may be you should just add the service name as the content of the file to know specifically which service corresponds to it.

You should do something like this:

try
{
$services = "AppHostSVC","FTPSVC", "IISAdmin"
    foreach($service in $services)
    {
        if((Get-Service $Service).Status -eq 'Stopped')
        {
           Get-Service $Service| Select-Object Name,Status,DisplayName| ConvertTo-Html |Out-File -FilePath "C:\checker.html" -Append -Force
        } 
        else
        {
            "$($Service) is not stopped"
        }

    }
}
catch
{
    $_.Exception.Message
}

Hope it helps.

  • Related