Out-File not working with Invoke-Command

I have the following ScriptBlock:

$ScriptBlock =  { ForEach($WebSite in $(get-website))
            {
                $ReportFile = "\\FileShare\WebLogs\FreeSpaceReport.csv"
                $LogLocation="$($Website.logFile.directory)\w3svc$($website.id)".replace("%SystemDrive%",$env:SystemDrive)
                $LogDrive = (get-item $LogLocation).psdrive.name
                $LogDriveFreeSpace = (get-psdrive $LogDrive).free/1gb
                $LogDriveFreeSpace = "{0:N2}" -f $LogDriveFreeSpace
                $Output = "$server $($WebSite.name) [$LogLocation] $LogDriveFreeSpace"
                $Output | Out-File $ReportFile -append
            }

            }

When I run this directly from $server, $ReportFile gets generated correctly.

However, when I run:

Invoke-Command -ComputerName $server  -ScriptBlock {$ScriptBlock}

, from a different server the report file does not get generated.

I tried replacing $ScriptBlock with just “get-website,” and this works, so I know I can invoke commands remotely.

I thought this might be a credential issue (on the file share) or double-hop issue, so I tried:

Invoke-Command -ComputerName $server  -ScriptBlock {$ScriptBlock} -Authentication CredSSP -Credential $Credential
, but this doesn't generate $ReportFile either.

Any ideas why $ReportFile isn’t getting created when using Invoke-Command to run $ScriptBlock on a remote system?

Put your get-website function in the scriptblock

I don’t follow. It’s already in the scriptblock?

Where does the Get-Website function reside? Is it available on the remote machine? Dan is saying that you need to place the function to be accessible in the scriptblock to ensure it’s available in the remote call session:

$ScriptBlock =  { 
    function Get-WebSite {
        #function code
    }

    ForEach($WebSite in $(get-website)) {
        $ReportFile = "\\FileShare\WebLogs\FreeSpaceReport.csv"
        $LogLocation="$($Website.logFile.directory)\w3svc$($website.id)".replace("%SystemDrive%",$env:SystemDrive)
        $LogDrive = (get-item $LogLocation).psdrive.name
        $LogDriveFreeSpace = (get-psdrive $LogDrive).free/1gb
        $LogDriveFreeSpace = "{0:N2}" -f $LogDriveFreeSpace
        $Output = "$server $($WebSite.name) [$LogLocation] $LogDriveFreeSpace"
        $Output | Out-File $ReportFile -append
    }
}

I still think you’re facing a double-hop problem because CredSSP needs to be properly enabled on the client and server to work and it is unsafe in my opinion because your credentials will be cached on the remote server for quite some time.

I wouldn’t attempt to write to a share from a remote server. My approach as outlined in below example is to gather all the required information from the remote sessions through objects being returned and process them in the session and on the machine that invoked the remote commands.

$Servers = @( 'Server1', 'Server2' )
$ReportFile = '\\FileShare\WebLogs\FreeSpaceReport.csv'

$ScriptBlock =  
{
    Import-Module -Name WebAdministration -Force -ErrorAction Stop
    
    foreach ($Website in $(Get-Website))
    {
        $LogLocation = "$($Website.LogFile.Directory)\w3svc$($Website.Id)".Replace('%SystemDrive%', $env:SystemDrive)
        $LogDrive = (Get-Item $LogLocation).PSDrive.Name
        $LogDriveFreeSpace = '{0:N2}' -f ((Get-PSDrive $LogDrive).Free / 1gb)

        [PSCustomObject]@{
            ComputerName = $env:COMPUTERNAME
            SiteName = $Website.Name
            LogLocation = $LogLocation
            LogDriveFreeSpace = $LogDriveFreeSpace
        }
    }
}

$Results = Invoke-Command -ComputerName $Servers -ScriptBlock $ScriptBlock

$Results | Select-Object -Property ComputerName, SiteName, LogLocation, LogDriveFreeSpace |
    Out-File $ReportFile -Append

=D get-website certainly sounds like a user defined function. Good catch.

I don’t believe that Get-Website is a user-defined function because the WebAdministration PowerShell module installed on every web server exports this function.

https://technet.microsoft.com/library/ee807832.aspx

I was saying it sounded like one, I didn’t even think to look it up=D

Nice catch guys…I did indeed need to define the Get-Website function inside the scriptblock.

Worked by adding “Import-Module -Name WebAdministration -Force -ErrorAction Stop.”

Thank you!

Daniel,

Does this line

$Results = Invoke-Command -ComputerName $Servers -ScriptBlock $ScriptBlock
execute the ScriptBlock on all of the servers in $Servers simultaneously, or does it loop through them one at a time?

Yes, Invoke-Command will execute the script block concurrently. The default limit is 32 but you can reduce or increase it with the -ThrottleLimit parameter. You can provide thousands of computer names and Invoke-Command will loop through them for you.

Check out the description of the ThrottleLimit parameter in the official documentation of the command.
https://technet.microsoft.com/en-us/library/hh849719.aspx