allig256
allig256

Reputation: 45

How to run a module within a Scriptblock in PowerShell?

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!

Upvotes: 0

Views: 1162

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

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!

Upvotes: 1

Related Questions