0

I am trying to run the following command on multiple machines on a LAN (not on a domain). When I run without the Invoke-Command on the local machine it works perfectly. When I try to invoke, it can no longer find the file path on the machine I am running the command to as it is looking at the remote directory. I cannot get a sharedrive to function for this purpose. I had a similar question for which a hash table was suggested and successfully implemented. I cannot figure out how I would do that with the below.

Invoke-Command -Computername $computers -Credential {
 Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate,InstallLocation | Format-Table -Property DisplayName,DisplayVersion,Publisher,InstallDate,InstallLocation -AutoSize | Out-File -Width 2048 "c:\scripts\ComputerInformation\SoftwareInformation\$env:COMPUTERNAME.software.txt"
        $applications = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
        foreach ($application in $applications) {
            $hostname = $env:COMPUTERNAME
            $DisplayName = $application.DisplayName
            $displayVersion = $application.DisplayVersion
            $HelpLink = $application.HelpLink
            $IdentifyingNumber = $application.PSChildName
            $InstallDate = $application.InstallDate
            $RegOwner = $null
            $vendor = $application.Publisher
            if ($DisplayName -ne $null -or $DisplayVersion -ne $null -or $HelpLink -ne $null -or $IdentifyingNumber -ne $null -or $InstallDate -ne $null -or $vendor -ne $null ) {
                Add-Content -Path 'c:\scripts\Inventories\SoftwareInventory.csv' "$hostname,$DisplayName,$DisplayVersion,$HelpLink,$IdentifyingNumber,$InstallDate,$RegOwner,$Vendor"
            }

Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate,InstallLocation | Format-Table -Property DisplayName,DisplayVersion,Publisher,InstallDate,InstallLocation -AutoSize | Out-File -Append -Width 2048 "c:\scripts\ComputerInformation\SoftwareInformation\$env:COMPUTERNAME.software.txt"
        $applications = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
        foreach ($application in $applications) {
            $hostname = $env:COMPUTERNAME
            $DisplayName = $application.DisplayName
            $displayVersion = $application.DisplayVersion
            $HelpLink = $application.HelpLink
            $IdentifyingNumber = $application.PSChildName
            $InstallDate = $application.InstallDate
            $RegOwner = $null
            $vendor = $application.Publisher      
            if ($DisplayName -ne $null -or $DisplayVersion -ne $null -or $HelpLink -ne $null -or $IdentifyingNumber -ne $null -or $InstallDate -ne $null -or $vendor -ne $null ) {
                Add-Content -Path 'c:\scripts\Inventories\SoftwareInventory.csv' "$hostname,$DisplayName,$DisplayVersion,$HelpLink,$IdentifyingNumber,$InstallDate,$RegOwner,$Vendor"
            }
}
Rachel5309
  • 53
  • 7
  • Why are you not using PowerShell redirection for this use case? [about_Redirection - PowerShell | Microsoft Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection) --- See also [Powershell logging from invoke-command](https://stackoverflow.com/questions/17198130/powershell-logging-from-invoke-command) – postanote Jun 24 '20 at 02:37
  • A separate note about best practices. You should put $null on the left side of the comparison. Documentation [here](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/RuleDocumentation/PossibleIncorrectComparisonWithNull.md). – Steven Jun 24 '20 at 17:20
  • @postanote I think [my answer](https://stackoverflow.com/a/62547887/4749264) is consistent with the one you cited. Let me know what you think. Thanks. – Steven Jun 24 '20 at 17:23
  • Good deal, @Steven and yeppers, you are on point relative to what I was directing the OP to utilize. – postanote Jun 24 '20 at 21:50

2 Answers2

1

Rachel,

Try it this way.

#--- Set the path for location of NetComps.txt ---
$NetPath = "\\MYBOOKLIVE\CMShared\Misc"
[Array]$Computer = Get-Content "$NetPath\NetComps.txt"

$OutPath = '\\DellXPS8920\BEKDocs-DellXPS8920\Test.txt'

ForEach ($Comp in $Computer) {

  $TCArgs = @{ComputerName = "$Comp"
              Quiet        = $True
              Count        = 1}

  If (Test-Connection @TCArgs) {
    
    Try {
       $ICArgs = @{ComputerName = "$Comp"
                   ScriptBlock  = {gci -Path "G:\BEKDocs\Transfer\*.*"}
                   ErrorAction  = "Stop"
                  }
       $x = Invoke-Command @ICArgs  | Out-String

       If ($x -eq "") {
         Add-Content -Path "$OutPath" "Computer: $Comp - Directory Found but EMPTY"
       }
       Else {Add-Content -Path "$OutPath" "$x"}

    }
    Catch {Add-Content -Path "$OutPath" "Computer: $Comp - Directory Not Found"}

  } #End If (Test...
}   #End ForEach...

HTH

RetiredGeek
  • 2,980
  • 1
  • 7
  • 21
  • I don't really understand how to use that to pull data with the `Get-ItemProperty` cmdlet. I need to be able to use `Invoke-Command` but still save all of the variables I have written out into an excel document on the machine I am running the script from and using a network share is not a possibility. I have my `$computers` location set and can get the script to work but only if I select certain objects and export to csv. When I do that, I don't get the hostname and null spaces that I need – Rachel5309 Jun 24 '20 at 00:50
  • I don't understand why you can't use a share on the computer you are running from? I don't know any other way to reference a location on your computer from the remote computers. One other option would be to build a large string. With your results and pass it back as the result of the Invoke-Command then write it to the file. Not very efficient through. – RetiredGeek Jun 24 '20 at 02:42
  • That is exactly what I had to do for my other scripts. I would love to be able to use a share but I may not be authorized to make that change on the system I will be working and I cant test it on the virtual LAN I am using now (it just will not work). I am writing scripts for both scenarios just so that I am fully prepared. – Rachel5309 Jun 24 '20 at 14:09
1

One way to fix this is to deal with the data locally after it's returned instead of when and where it's being processed / gathered.

There are a few things I'm confused about. You are gathering up some string data and trying to get it into a file, then trying to get somewhat redundant other data into a second comma delimited file. Also you are missing an argument for credential.

At any rate, for my example / demo I'm going to assume we only need the CSV data.

$ScriptBlock =
{
    $Properties = 
    @(
        'DisplayName'
        'DisplayVersion' 
        'HelpLink'
        'IdentifyingNumber'
        'InstallDate'
        'vendor'
    )
    
    Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
    ForEach-Object{
        :Inner ForEach( $Property in $Properties )
        {
            If( $_.$Property )
            {
                [PSCustomObject][Ordered]@{
                    hostname          = $env:COMPUTERNAME
                    DisplayName       = $_.DisplayName
                    DisplayVersion    = $_.DisplayVersion
                    HelpLink          = $_.HelpLink
                    IdentifyingNumber = $_.PSChildName
                    InstallDate       = $_.InstallDate
                    RegOwner          = $null
                    Vendor            = $_.Publisher
                }
            Break Inner
            }
        }
    }
}

Invoke-Command -Computername $computers -ScriptBlock $ScriptBlock |
Export-Csv -Append 'c:\scripts\Inventories\SoftwareInventory.csv' -NoTypeInformation

Note: I'm in a domain environment so I left -Credential off.

So I'm returning custom objects that are suitable for direct output to CSV. But the key point is to emit what you want inside the script block remotely, let PowerShell return it to the local session where you can then work with it locally. If you have the data local then local file, no problem.

A few other notes mostly sugar:

  1. I parked the script block in a variable. This is just a preference of mine, I find it easier to work with.
  2. Instead of using 1 long if condition I stored the properties in an array. Then later I looped the array testing each property. If any property hits I emit the object then break the inner loop, so we don't get duplicates. I don't know why you need the if condition in the first place but this just seemed like a more eloquent way to do it.

Update

Responding your comments, here's an example that generates sets of files:

$CsvProps =
@(
    'hostname'
    'DisplayName'
    'DisplayVersion' 
    'HelpLink'
    'IdentifyingNumber'
    'InstallDate'
    'vendor'
)

$TxtProps =
@(
    'DisplayName'
    'DisplayVersion'
    'Publisher'
    'InstallDate'
    'InstallLocation'
)

$ScriptBlock =
{
    Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
    ForEach-Object{
        [PSCustomObject][Ordered]@{
            hostname          = $env:COMPUTERNAME
            DisplayName       = $_.DisplayName
            DisplayVersion    = $_.DisplayVersion
            HelpLink          = $_.HelpLink
            IdentifyingNumber = $_.PSChildName
            InstallDate       = $_.InstallDate
            RegOwner          = $null
            Vendor            = $_.Publisher
            Publisher         = $_.Publisher
            InstallLocation   = $_.InstallLocation
        }
    }
}

$ReturnObjects = Invoke-Command -Computername $computers -ScriptBlock $ScriptBlock

# Create Textfiles for each host:
$ReturnObjects | 
Group-Object -Property hostname |
ForEach-Object{
    $FileName = $_.Name # This'll be the hostname, it was the group property.
    $_.Group |     
        Format-Table $TxtProps |
        Out-File -Width 2048 "c:\scripts\ComputerInformation\SoftwareInformation\$FileName.software.txt"
    }

# Create CSV file:
$ReturnObjects |
ForEach-Object{
    :Inner ForEach( $Property in $_.PSObject.Properties.Name )
    {   # Write $_ to the pipeline if any property is valid
        If( $Property -eq 'hostname' ) { Continue Inner } # They will all have hostname.
        ElseIf( $_.$Property )
        {
            $_ # Write $_ down the pipeline
            Break Inner
        }
    }
    } |
Select-Object $CsvProps |
Export-Csv 'c:\scripts\Inventories\SoftwareInventory.csv' -NoTypeInformation -Append

So this is a bit different than the previous version:

  1. The object in the script block has all the properties needed for both of the desired outputs.
  2. This is different than the method I mentioned earlier. I thought it was better to store the results in a variable, rather than try to extract both goals in 1 ForEach-Object loop.
  3. There is no if logic in the script block. This is so we can get back all objects and work with them locally so we can do the right filtering respective to the text & csv files. Again to be able to write the file locally we need the data local.
  4. Since there are different properties for the text files versus the CSV, there are different collections for each.
  5. The way I filter data for the CSV file is different, but the principal is the same.

I realize my demo is a departure from your sample. However, I hope it demonstrates the key points and helps you get past the issue.

FYI: If you have troubles because you're environment is a workgroup, check out Secrets of PowerShell Remoting. I believe there's a section on remoting in a workgroup.

Let me know. Thanks.

Steven
  • 6,817
  • 1
  • 14
  • 14
  • Thank you. I will try that now. I do need the redundant variables that are null as the intent is to create a csv that can be easily copied directly into an excel file that has additional parameters not provided through these commands. – Rachel5309 Jun 24 '20 at 14:14
  • OK I'll add that example now. – Steven Jun 24 '20 at 14:20
  • Got it all set up and tried running the `Invoke-Command` `foreach($computer in $computers)` looked great when it pulled the local machine but didn't pull any information from the remote. I pulled out the foreach and ran just on the remote machine and got a blank excel in return. Didn't receive any errors.... – Rachel5309 Jun 24 '20 at 14:47
  • I added the second example. Seems to work in my environment. My examples aren't design to run Invoke-Command from with in a `ForEach` loop. So I'm not sure what you mean. In a technical sense this should work, but I can't be certain what's going on in your environment. I'm a bit concerned it will work differently in a workgroup environment. COme to think of it I'll add links to some remoting documentation. Also, not to be pushy, but if you're comfortable with it, please mark the question as answered. Thanks! – Steven Jun 24 '20 at 15:49
  • I was able to get the remote computer to pull information but only if I commented out the `If($_.($Property))` and `Break Inner`. Of course then I was left with an entry for each property on the csv...so 7 entries for each registry key. I will definitely mark this as answered as soon as I get it down. Sorry...I am really new to this and definitely appreciate your help. This looks so much cleaner than what I was working with. – Rachel5309 Jun 24 '20 at 16:27
  • I tried your second example and it pulled a blank csv. If I remove the Break Inner, it gives pulls information but puts multiple of the same instance in the excel. – Rachel5309 Jun 25 '20 at 13:35
  • Look for notes example 1, Break statement is to prevent duplicates. So, that part is performing as expected. Maybe I'm missing something, code is working in my domain env. Logic looks sound; for each return object, loop through that objects property names, skiping hostname which is always populated. `ElseIf` any prop is valid emit the object ( $_ ) to pipeline, then break the inner loop which jumps back to the outer, moving to the next return object & preventing duplicates. The concepts are solid respected user @postanote confirmed. Not sure what else I can do... Let me know... – Steven Jun 25 '20 at 14:04
  • Got it!!! Had to remove the `Break Inner`. Thank you!!!! – Rachel5309 Jun 25 '20 at 15:23