Forum Discussion

gower666's avatar
gower666
Copper Contributor
Jul 03, 2024

Powershell help - export to CSV not working - exporting EXE version number - help required...

Hi, I am struggling to get this code below to output to a CSV file. I know I am doing something wrong.

I need to get a version number from an EXE file on multiple devices.

Thanks in advance all 🙂


Script Details :

$ExportPath = "C:\Temp"
$ComputerName = Get-Content “C:\Temp\DeviceList.txt”

foreach ($Computer in $ComputerName){

$filePath = “\$computer\c$\Admin\program.exe”
$fileVersion = (Get-Command $filePath).FileVersionInfo.FileVersion

Write-Host “$computer The file version is $fileVersion”
}

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    gower666 

     

    Hi, Gary.

     

    There's at least three different ways you can retrieve file remote file information:

     

    1. WinRM;
    2. SMB
    3. WMI.

     

    I'll cover the first two with examples. WMI shares the benefits of WinRM but I don't use WMI much these days, so I'm skipping it.

     

    WinRM

    Windows Remote Management has been around since about Vista/Server 2008, meaning it's very well established. That said, many people can't be bothered enabling it for remote connections and even fewer configure it to run securely over TLS. (The best and easiest way to enable WinRM for remote connections in a domain environment is via Group Policy, but that's beyond the scope of this discussion.)

     

    So, you may or may not be well-positioned to leverage WinRM.

     

    This example leverages Invoke-Command, which is far more efficient than using a foreach statement since Invoke-Command can process multiple computers in parallel (i.e. asynchronously), while foreach cannot (it processes one after the other, or "synchronously" - and I'm deliberately focusing on Windows PowerShell here, as it ships "out of the box", where PowerShell does not).

     

    Example

    This version would run over when using a non-TLS WinRM endpoint.

     

    $ExportPath = "D:\Data\Temp\forum\fileInfo.csv";
    Invoke-Command -ComputerName (Get-Content -Path "D:\Data\temp\forum\forum.txt") -ScriptBlock {
        Get-Item -Path "C:\Data\SteamSetup.exe" -ErrorAction:SilentlyContinue |
            ForEach-Object {
                [PSCustomObject] @{
                    FileName = $_.VersionInfo.FileName;
                    Version = $_.VersionInfo.FileVersion;
                }
            }
    } |
        Select-Object -Property PSComputerName, Version, FileName |
            Export-Csv -Path $ExportPath -NoTypeInformation;

     

    Output

     

    SMB

    SMB is best known as "file and print", which is the approach you've taken in your initial example.

     

    SMB is almost always enabled and accessible by default, and is positively ancient - it's been around in Windows NT since inception, which puts it around the early 1990's (though there have been multiple major revisions and it's still a very useful protocol).

     

    The downside to the SMB approach is that it's most commonly going to be handled synchronously via a foreach statement. You can put extra effort into the coding to adopt an asynchronous approach, but most people go for easy ahead of efficient, meaning that rarely happens.

     

    Because each computer is processed one at a time, this method is becomes more inefficient as more clients are unreachable for whatever reason.

     

    Example
    $ExportPath = "D:\Data\Temp\forum\fileInfo.csv";
    Get-Content -Path "D:\Data\temp\forum\forum.txt" |
        ForEach-Object {
            Get-Item -Path "\\$_\c$\\Data\SteamSetup.exe" -ErrorAction:SilentlyContinue |
                ForEach-Object {
                    [PSCustomObject] @{
                        FileName = $_.VersionInfo.FileName;
                        Version = $_.VersionInfo.FileVersion;
                    }
                }
        } |
            Select-Object -Property Version, FileName |
                Export-Csv -Path $ExportPath -NoTypeInformation;

     

    Output

     

    WMI

    As mentioned, WMI is much the same as WinRM where the primary benefit is that is can process multiple computers at once via the Get-CimInstance commandlet, which - like Invoke-Command - can take multiple computer names in the -ComputerName parameter.

     

    So, like the WinRM approach, it scales better when checking large numbers of computers.

     

    Cheers,

    Lain

    • gower666's avatar
      gower666
      Copper Contributor

      LainRobertson wow that was quick, thank you so much, that worked like a charm (2nd script). Thank you so so much, have a lovey day 🙂

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    gower666 

     

    One other quick explanation is the difference between Write-Host and Write-Object - which is the default pathway used when "outputting" to a device (be that file or screen):

     

    • Write-Host writes strings to the information stream, which - put most plainly - means writing messages to the screen, not outputting data to the default stream;
    • Write-Object writes an object to the standard output stream.

     

    It's actually more complex than that but simple explanations are a good place to start.

     

    The point is that you want to leverage writing objects to the data stream, not strings to the display.

     

    For example, if you have the following object:

     

     

    And you send that output to a CSV file, as shown below:

     

    [PSCustomObject] @{ city = "Perth"; PostCode = 6000; } |
       Export-Csv -NoTypeInformation -Path "D:\Data\Temp\Forum\forum.csv";


    You get a well-formed CSV file, as we wrote an object out on the data stream (or pipeline😞

     

     

    But if we write a string (via the information stream) using:

     

    Write-Host -Object '"Perth","6000"' | Export-Csv -NoTypeInformation -Path "D:\Data\Temp\Forum\forum.csv";

     

    You can see the output has actually gone to the screen:

     

     

    And nothing has gone to the file, resulting in an empty file:

     

     

     

    Cheers,

    Lain

    • gower666's avatar
      gower666
      Copper Contributor

      Morning Lain,

      Can you use the same script to extract a files modified date on remote machines?

      Similar to this command line (sorry I am new to PowerShell and I really appreciate your help).

      Script Example (I want this 2nd script to run against a list of computers in a txt file, and output / append the created file date for a specific file to a csv file):

       

      -----------

      Script 1 :

      -----------


      $filePath = "C:\temp\results.txt"
      $creationDate = [System.IO.File]::GetCreationTime($filePath)
      Write-Output "The file was created on: $creationDate"

       

       

      There is also this 2nd script which does the local machine, however I am struggling to get this to work against remote machines (40 in total).

       

      ----------

      Script 2 :

      -----------

       

      $files = 'C:\path\to\first\dll','C:\path\to\second\dll' $computer = New-Object System.Object $computer | Add-Member -type NoteProperty -Name CompName -value $env:ComputerName foreach ($file in $files){ $lastWrite = Get-Item $file | select LastWriteTime $computer | Add-Member -type NoteProperty -Name $file -value $lastWrite.LastWriteTime } $computer | Export-Csv -Path "\\path\to\server\share" -Append -NoTypeInformation

      • LainRobertson's avatar
        LainRobertson
        Silver Contributor

        gower666 

         

        Hi, Gary.

         

        Please ensure you post sample code in a code box, as it's impossible to make sense of when it's dumped as unformatted text in the thread. This is what the second script looks like after posting:

         

         

        And when that wall of text is copied-and-pasted into an IDE, it comes through as a single line, which is just as unreadable - particularly if the semicolon hasn't been used to demarcate separate commands.

         

        Moving onto your new question, you can easily piggyback off the example scripts above.

         

        While I'm not a fan of the SMB approach as it's slow and doesn't scale well, here's a modified SMB example that gives you the creation and modification timestamps.

         

        For the creation and modification dates, we're simply grabbing them from the existing file object returned from Get-Item.

         

        I've also demonstrated how you can check multiple files through pre-pending a string array in front of the Get-Item, where the relative file paths are specified as separate strings.

         

        There's other, neater ways this can done, but by sticking as close as is possible to the original scripts above, I'm hoping it helps understand the concepts underpinning the changes, the most important being that you're working with objects.

         

        Before getting into the example script, notice how there's multiple timestamps to choose from on the file objects returned by Get-Item? This data is what we're going to leverage.

         

         

        Example
        $ExportPath = "D:\Data\Temp\forum\fileInfo.csv";
        
        Get-Content -Path "D:\Data\temp\forum\forum.txt" |
            ForEach-Object {
                $Name = $_;
        
                @(
                    "c$\Data\SteamSetup.exe"
                    , "c$\Data\query.txt"
                    , "c$\Data\secedit.inf"
                ) |
                    ForEach-Object {
                        Get-Item -Path "\\$Name\$_" -ErrorAction:SilentlyContinue |
                            ForEach-Object {
                                [PSCustomObject] @{
                                    Version = $_.VersionInfo.FileVersion;
                                    WhenCreated = $_.CreationTime;
                                    WhenChanged = $_.LastWriteTime;
                                    FileName = $_.VersionInfo.FileName;
                                }
                            }
                    }
            } |
                Export-Csv -Path $ExportPath -NoTypeInformation;

         

        Output

         

         

        Cheers,

        Lain

Resources