0

I am currently trying to import a .psm1 file dynamically into a script block to execute it.

I am using parallelisation along with jobs as I need to trigger several modules simultaneously as different users.

This is the code:

    $tasksToRun | ForEach-Object -Parallel {

        $ScriptBlock = { 
            param ($scriptName, $Logger, $GlobalConfig, $scriptsRootFolder )
            Write-Output ("hello $($scriptsRootFolder)\tasks\$($scriptName)")
            Import-Module ("$($scriptsRootFolder)\tasks\$($scriptName)")
            & $scriptName -Logger $Logger -GlobalConfig $GlobalConfig
        }
    
        $job = Start-Job -scriptblock $ScriptBlock `
            -credential $Cred -Name $_ `
            -ArgumentList ($_, $using:Logger, $using:globalConfig, $using:scriptsRootFolder) `
    
        Write-Host ("Running task $_")
    
        $job | Wait-job -Timeout $using:timeout
    
        if ($job.State -eq 'Running') {
            # Job is still running, stop it
            $job.StopJob()
            Write-Host "Stopped $($job.Name) task as it took too long"
        }
        else {
            # Job completed normally, get the results
            $job | Receive-Job
            Write-Host "Finished task $($job.Name)"
        }
    }

The logger variable is a hashtable as defined here:

    $Logger = @{
        generalLog         = $function:Logger
        certificateLog     = $function:LoggerCertificate
        alertLog           = $function:LoggerAlert
        endpointServiceLog = $function:LoggerEndpointService
    }

Currently, it is erroring with the following:

ObjectNotFound: The term 
' blah blah blah, this is the code straight from the logger function ' 
is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

The logger function servers the purpose of logging to a file in a specific way, it is generalised to that it can be used across many tasks.

A cut down example of a logger (probably won't compile, just deleted a bunch of lines to give you the general idea):

function LoggerEndpointService {

    param (
        # The full service name.
        [string]$ServiceFullName,

        # The unique identifier of the service assigned by the operating system.
        [string]$ServiceId,

        # The description of the service.
        [string]$Description,

        # The friendly service name.
        [string]$ServiceFriendlyName,

        # The start mode for the service. (disabled, manual, auto)
        [string]$StartMode,

        # The status of the service. (critical, started, stopped, warning)
        [string]$Status,

        # The user account associated with the service.
        [string]$User,

        # The vendor and product name of the Endpoint solution that reported the event, such as Carbon Black Cb Response. 
        [string]$VendorProduct
    )
        
    $ServiceFullName = If ([string]::IsNullOrEmpty($ServiceFullName)) { "" } Else { $ServiceFullName }
    $ServiceId = If ([string]::IsNullOrEmpty($ServiceId)) { "" } Else { $ServiceId }
    $ServiceFriendlyName = If ([string]::IsNullOrEmpty($ServiceFriendlyName)) { "" } Else { $ServServiceFriendlyNameiceName }
    $StartMode = If ([string]::IsNullOrEmpty($StartMode)) { "" } Else { $StartMode }
    $Status = If ([string]::IsNullOrEmpty($Status)) { "" } Else { $Status }
    $User = If ([string]::IsNullOrEmpty($User)) { "" } Else { $User }
    $Description = If ([string]::IsNullOrEmpty($Description)) { "" } Else { $Description }
    $VendorProduct = If ([string]::IsNullOrEmpty($VendorProduct)) { "" } Else { $VendorProduct }
    $EventTimeStamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK"

    $Delay = 100
    For ($i = 0; $i -lt 30; $i++) {
        try {
            $logLine = "{{timestamp=""{0}"" dest=""{1}"" description=""{2}"" service=""{3}"" service_id=""{4}"""  `
            + "service_name=""{5}"" start_mode=""{6}"" vendor_product=""{7}"" user=""{8}""  status=""{9}""}}"
            $logLine -f $EventTimeStamp, $env:ComputerName, $Description, $ServiceFullName, $ServiceId, $ServiceFriendlyName, $StartMode, $VendorProduct, $User, $Status | Add-Content $LogFile -ErrorAction Stop
            break;
        }
        catch {
            Start-Sleep -Milliseconds $Delay
        }
        if ($i -eq 29) {
            Write-Error "Alert logger failed to log, likely due to Splunk holding the file, check eventlog for details." -ErrorAction Continue
            if ([System.Diagnostics.EventLog]::SourceExists("SDOLiveScripts") -eq $False) {
                Write-Host "Doesn't exist"
                New-EventLog -LogName Application -Source "SDOLiveScripts"
            }
            Write-EventLog -LogName "Application" -Source "SDOLiveScripts" `
                -EventID 1337 `
                -EntryType Error `
                -Message "Failed to log to file $_.Exception.InnerException.Message" `
                -ErrorAction Continue
        }
    }    
}

Export-ModuleMember -Function LoggerEndpointService

If anyone could help that'd be great, thank you!

7
  • 1
    Start-Job creates a new process, and due to the way serialization of reference types is implemented, $Logger.generalLog is no longer an executable function by the time the job runs - it's just a string. You can recreate the function based on the definition, but it'll only work if it doesn't reference any other object in the calling scope. Can you show one of the Logger function implementations? Commented Oct 6, 2020 at 20:39
  • Sure thing, I just edited the main post to give you an idea of what they look like. Commented Oct 6, 2020 at 20:50
  • 1
    Well, if you deleted a bunch of code then I can't really tell you whether you'd run into problems if you try to re-create the function in the job :) Commented Oct 6, 2020 at 20:59
  • I only deleted non-function parts like variables, I'll chuck it all up for ya, just didn't want to clutter the post sorry! Commented Oct 6, 2020 at 21:05
  • 1
    Right, so recreating the function in the job won't work, because $LogFile won't exist Commented Oct 6, 2020 at 21:08

1 Answer 1

1

As mentioned in the comments, PowerShell Jobs execute in separate processes and you can't share live objects across process boundaries.

By the time the job executes, $Logger.generalLog is no longer a reference to the scriptblock registered as the Logger function in the calling process - it's just a string, containing the definition of the source function.

You can re-create it from the source code:

$actualLogger = [scriptblock]::Create($Logger.generalLog)

or, in your case, to recreate all of them:

@($Logger.Keys) |ForEach-Object { $Logger[$_] = [scriptblock]::Create($Logger[$_]) }

This will only work if the logging functions are completely independent of their environment - any references to variables in the calling scope or belonging to the source module will fail to resolve!

Sign up to request clarification or add additional context in comments.

1 Comment

Beautiful, it works when used within the scriptblock definition. Thank you very much for all the help! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.