I have a questions regard what the difference is when accepting values through either a Pipeline, or Parameter input.
I ask because:
Accepting values by pipeline in my script, works.
Accepting values using the parameter argument such as, "-ComputerName" sort of works.
I say sort of because, It does take my input from the same variable up until theres one that it cant connect to, then it stops the script. Is there a reason for this?
Heres my script for anyone curious:
Function Set-DWRCVersion {
<#
NAME
Set-DWRCVersion
SYNTAX
Set-DWRCVersion [[-ComputerName] <string[]>] [<CommonParameters>]
EXAMPLES
Set-DWRCVersion -ComputerName LocalHost,ComputerOne
'LocalHost','ComputerOne' | Set-DWRCVersion
$CritNames | Set-DWRCVersion
$CritNames | Set-DWRCVersion |Export-Csv "$($env:USERPROFILE.Substring(0,20))\Desktop\NewCrit.CSV" -Force -NoTypeInformation
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipeLineByPropertyName=$true)]
[ValidateLength(1,15)]
[Alias('Comp','CN','name')]
[string[]]$ComputerName)
Process{
try{
[string]$Path = "C:\Users\Abraham\Desktop\dwrcs" #Dameware File Path (current)
foreach($Computer in $ComputerName){
$PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop
$TestPath = Invoke-Command -Session $PSSession -ScriptBlock { Test-Path -Path "C:\Windows\dwrcs" }
if($TestPath -eq $false){
Copy-Item -Path $Path -Destination "\\$Computer\C$\Windows" -Recurse -Force }
#Start-Sleep -Seconds 1
$EXEVersion = Invoke-Command -Session $PSSession -ScriptBlock { [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\dwrcs\DWRCS.EXE").FileVersion }
$DLLVersion = Invoke-Command -Session $PSSession -ScriptBlock { [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\dwrcs\DWRCRSS.dll").FileVersion }
if($EXEVersion -notmatch '12.1.2.584'){
[PSCustomObject] #{
"Computer Name" = $Computer
"Status" = "Online"
"DWRC Version" = $EXEVersion
"DWRCRSS DLL" = $DLLVersion
"DWRC Check" = "Not up to date" }
""
Write-Output -InputObject "Version not current"
Write-Output -InputObject "Stopping Service. . ."
Invoke-Command -Session $PSSession -ScriptBlock {
Stop-Service -Name dwmrcs;
Get-Item -Path C:\windows\dwrcs | Rename-Item -NewName "dwrcs.old" }
Remove-Item -Path "\\$Computer\c$\Windows\dwrcs.old" -Recurse -Force -ErrorAction SilentlyContinue
#Start-Sleep 1
Write-Output -InputObject "Copying new files over. . ."
Copy-Item -Path $Path -Destination "\\$Computer\C$\Windows" -Recurse -Force
Write-Output -InputObject "Starting Service. . ."
Invoke-Command -Session $PSSession -ScriptBlock { Start-Service -Name dwmrcs }
}
elseif($EXEVersion -match '12.1.2.584') {
[PSCustomObject] #{
"Computer Name" = $Computer
"Status" = "Online"
"DWRC Version" = $EXEVersion
"DWRCRSS DLL" = $DLLVersion
"Version Check" = "Up to Date" }
}
else { Write-Output -InputObject "Error Occured"
Throw "$($Error[0].Exception.Message)" }
}
} Catch {
[PSCustomObject] #{
"Computer Name" = $Computer
"Status" = "Offline"
"DWRC Version" = $null
"DWRCRSS DLL" = $null
"Version Check" = $null }
} Finally {
if ($PSSession) {
Get-PSSession | Remove-PSSession }
}
}
}
Overall, its a pretty simple script where it will get some remote file versions for me. I am just not sure why accepting values from the pipeline doesn't stop the script, but inputting them using the regular Parameter does.
Im importing from a csv to get the names using the following:
$Crit = Import-Csv -Path "$($env:USERPROFILE.Substring(0,20))\Desktop\med.csv"
$CritNames = $Crit.'NetBios Name' -replace "AREA51\\",""
<# Names return like so:
COmputerOne
COmputerTwo
COmputerThr
Etc.. #>
And when running it using the pipeline input: $CritNames | Set-DWRCVersion which works but, Set-DWRCVersion -ComputerName $CritNames doesn't; well it does up until it hits an offline computer then stops the script.
Is there something im missing? Can someone much smarter than me edumacate me?(:
You can solve this by moving the try/catch/finally statement inside the foreach loop:
process {
foreach($Computer in $ComputerName){
try {
# ...
}
catch {
# ...
}
finally {
# ...
}
}
}
Why would this make a difference?
When explicitly binding a value via -ComputerName, the process block is only invoked once - but when you supply input via the pipeline, the process block is invoked once per input item.
You can observe this behavior with a simple test function like this:
function Test-ProcessInvocation {
param(
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]$ComputerName
)
process {
"Running process block with $($ComputerName.Length) input arguments: [$ComputerName]"
}
}
Running this with both input modes will make this behavior clearly visible:
PS ~> Test-ProcessInvocation -ComputerName 1,2,3
Running process block with 3 input arguments: [1 2 3]
PS ~> 1,2,3 |Test-ProcessInvocation
Running process block with 1 input arguments: [1]
Running process block with 1 input arguments: [2]
Running process block with 1 input arguments: [3]
This means that running Set-DWRCVersion -ComputerName Computer1,Computer2 translates to this sequence of statements:
# Single invocation of `process` block with $ComputerName = all input values at once
try {
foreach($Computer in 'Computer1','Computer2'){
}
}
catch {}
finally {}
Running 'Computer1','Computer2' |Set-DWRCVersion on the other hand:
# `process` block repeats per input item
try {
foreach($Computer in 'Computer1'){
}
}
catch {}
finally {}
try {
foreach($Computer in 'Computer2'){
}
}
catch {}
finally {}
As a result, throwing an error inside the foreach loop when invoking via the pipeline never "skips" any items, because the loop is only ever operating on one at a time.
By inverting the relationship between the loop and the try/catch statement, any errors will now have been captured and handled inside the loop, and it will no longer skip the remaining input values in $ComputerName.
Related
I am trying to write this script to restart computers only if they are Offline. The script for getting user infomration works but I cannot get the variable values for the restart portion at the bottom of the script. Does anyone have a suggestion? I am somewhat new to Powershell, but writing code. Example of my script follows:
Function Get-LoggedOnUser
{
Param
(
$ComputerName = $env:COMPUTERNAME,
$Credential
)
Function Test-RemoteRegistry
{
Param
(
[Parameter(Mandatory = $False)]
[switch]$Enable
,
[Parameter(Mandatory = $False)]
[switch]$Disable
,
[Parameter(ValueFromPipeline=$True)]
[String[]]$ComputerName = $env:COMPUTERNAME
)
Begin
{
$PipelineInput = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and (-not $ComputerName)
Function Test ($Computer)
{
Try
{
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $Computer) | Out-Null
#20ms faster than Get-Service per computer! Not sure how to handle/check things like the firewall though...
#If we hit here without error Remote Reg is enabled.
If ($Disable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Stopped -ErrorAction Stop
Return $False
#If we hit here without error Remote Reg is now disabled.
}
Catch
{
Return $True
#If we hit here, we couldn't stop remote registry.
}
}
Else
{
Return $True
}
}
Catch
{
If ($Enable)
{
Try
{
Get-Service -Name RemoteRegistry -ComputerName $Computer | Set-Service -Status Running -ErrorAction Stop
Return $True
#If we hit here without error Remote Reg is now enabled.
}
Catch
{
Return $False
#If we hit here, we couldn't start remote registry.
}
}
Else
{
Return $False
#If we hit here remote registry is disabled.
}
}
}
}
Process
{
If ($PipelineInput)
{
Test $_
}
Else
{
$ComputerName | ForEach-Object {
Test $_
}
}
}
}
Foreach ($Computer in $Computername)
{
$Online = $False
$User = $False
$Locked = $False
If (Test-Connection $Computer -Count 2 -Quiet)
{
$Online = $True
If ($Credential)
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer -Credential $Credential | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
Else
{
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computer | Select-Object -ExpandProperty UserName -ErrorAction Stop
}
If (Test-RemoteRegistry -Enable -ComputerName $Computer)
{
If ((Get-Process logonui -ComputerName $Computer -ErrorAction SilentlyContinue) -and ($user))
{
$Locked = $True
}
}
}
$Output = New-Object PSObject
$Output | Add-Member noteproperty ComputerName $Computer
$Output | Add-Member noteproperty Online $Online
$Output | Add-Member noteproperty Username $User
$Output | Add-Member noteproperty Locked $Locked
$Output
}
}
Get-LoggedOnUser
If (($Online) -eq $False)
{Shutdown /r t 0 /m \\$Computername}
ELSE
{Write-host 'HELLO $Online $Computername'}
I just want this for a single user as I am using PDQ Inventory to roll out the script. The variables at the end of the script are $null?
Variables defined in a child scope - in which functions run by default - are never seen by the calling scope. See the conceptual about_Scopes help topic
It's best for functions to communicate values to the caller via their output ("return value"), which you're function is already doing: it outputs objects whose properties contain the values of interest.
Therefore:
Get-LoggedOnUser |
ForEach-Object { # Loop over all output objects
# Refer to the object at hand via the automatic $_ variable.
# Note the use of "..." (expandable strings) so as to support
# expansion (string interpolation).
if (-not $_.Online) { Shutdown /r t 0 /m "\\$($_.ComputerName)" }
else { "HELLO $($_.Online) $($_.ComputerName)" }
}
FYI: I'm very new to PS and I'm using this as a learning opportunity. Again, I'm trying to find a
specific application on a list of multiple remote devices and determine the version number of the
application on their corresponding host system. I attempted this via a registry query (found this to
be challenging) and then I used Get-WMIObject. As of now, I'm working with this as my script. It's
not producing any output; instead, it returns to the command prompt with no errors or messages.
Script to find specific application and version in multiple remote devices:
$Servers = Get-Content -Path C:\\files\Serverlist.txt
$CIMSession = New-CIMSession -ComputerName $Servers Get-Credentials
$Vendor = "App Name"
foreach($Serv in $Servers) {
If(Test-Connection -ComputerName $Serv -Count 1 -Quiet) {
$Status = Get-Ciminstance Win32_Product -Computername $Serv | Where-object {$_.Version -contains
$Vendor}
if($Status) {
Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
I also tried adjusting the following section of the script as shown below but it presented me with the error "Get-CimInstance : Access is denied." Is this error message due to group policy or so? I am able to remote into the device corresponding to the message via RDP.
if($Status) {
$Servers + " - "
$Status | Out-file -Filepath C:\\files\AppVerResults.txt
}
}
}
Should I go about it via invoke-command or registry query? I'm slowly picking things up so I'll continue my research but I was hoping to get some advice in the meantime.
I still believe searching the registry is the easier way to go unless you have the specific file path for the .exe.
Use this function to find software on a remote, or local PC. Theres a filter option by specifying -SoftwareName (to look for).
Find-Software -ComputerName Remote_ComputerName -SoftwareName 'SQL'
Also accepts pipeline input, as well as multiple computer names to query for.
Find-Software -ComputerName ComputerOne, ComputerTwo, ComputerThree -SoftwareName 'SQL'
'ComputerOne','ComputerTwo' | Find-Software -SoftwareName 'SQL'
Exporting is also allowed by piping to an Export-* cmdlet.
Heres the code:
Function Find-Software {
[cmdletBinding()]
Param(
[Parameter(Mandatory=$false,
ValueFromPipeLine=$true,
ValueFromPipeLineByPropertyName=$true)]
[Alias('cn','name')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$false)]
[String]$SoftwareName
)
Begin{
#Get Computer Names to check software version for
$Server_List = Get-Content -Path "C:\files\Serverlist.txt"
#Get Credentials for Script Scope once.
$Credentials = Get-Credential
}
Process{
if($PSBoundParameters.ContainsKey('SoftwareName')){
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Where-Object -FilterScript {$_.DisplayName -match $SoftwareName} | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
if($Software){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
} else {
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = "Not found"
" Version " = $null
}
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} else {
foreach($Computer in $ComputerName){
Try{
$PSSession = New-PSSession -ComputerName $Computer -Credential $Credentials -EnableNetworkAccess -ErrorAction Stop
$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" } -Session $PSSession
$Software_List = $Software_List | Sort-Object -Property DisplayName
foreach($Software in $Software_List){
[PSCustomObject]#{
"Computer Name" = $Computer
"Software Name" = $Software.DisplayName
" Version " = $Software.DisplayVersion
}
}
} Catch {
"Unable to connect to PC: $Computer"
"Error: $($Error[0].Message.Split('.')[1].Trim())"
}
}
} #end ELSE statement
} #end PROCESS block
End {
if(Get-PSSession){
Get-PSSession | Remove-PSSession
}
} #end END block - Perform Session Clean Up
} #end FUNCTION
Simply modify it to fit your needs :)
I'm attempting to develop a script with PowerShell to remotely install/update flash player for multiple machines. No matter what I do, I can't get the install to work properly at all. I'm very limited with my tools so I have to use PowerShell, and the MSI install of Flashplayer. I'll post my script below, any help at all would be greatly appreciated.
$Computers = Get-Content C:\Users\name\Desktop\flash.txt
(tried these 3 methods to install none work)
$install = #("/a","/i", "\\$Computer\c$\temp\flash\install_flash_player_32_plugin.msi", "/qn","/norestart")
Invoke-Command -ComputerName $Computer -ScriptBlock {Start-Process "Msiexec" -arg "$using:install" -Wait -PassThru} -Filepath msiexec.exe
#This returns with "invoke-command: parameter set cannot be resolved using the specified named parameters"
Invoke-Command -ComputerName $computer -ScriptBlock {Start-Process -Filepath msiexec.exe "$using:install" -Wait -PassThru} -Filepath msiexec.exe
#this returns the same error.
Invoke-Command -ComputerName $Computer -ScriptBlock {start-process msiexec -argumentlist #('/a','/i','"\\$Computer\c$\temp\flash\install_flash_player_32_plugin.msi"','/qn')}
#this seemingly skips the install entirely.
I've used similar scripts for other programs and had no problems installing them, but none of the methods I use or have researched are working properly.
This should do the trick, I'll explain why it wasn't working bellow:
$Computers = Get-Content C:\Users\name\Desktop\flash.txt
$params = '/i <path to AcroPro.msi> LANG_LIST=en_US TRANSFORMS="1033.mst" /qb'
$Computers | % {
Invoke-Command -ScriptBlock {
Param(
[Parameter(Mandatory=$true,Position=0)]
[String]$arguments
)
return Start-Process msiexec.exe -ArgumentList $arguments -Wait -PassThru
} -ComputerName $_ -ArgumentList $params
}
So, it wasn't working because the ScriptBlock on Invoke-Command cant see variables that you've declared on your powershell session, think of it like you are walking to that remote computer and inputting that code by hand, you wont have the value (metaphor).
I did a few more changes:
I moved all params into 1 single string, no need to have them in array.
Added $Computers | to iterate through computer names.
Removed FilePath as this is meant to be used differently, documentation(Example #1).
Set $MinutesToWait to whatever amount of minutes you want.
No need to try to pass msiexec, as it comes with windows the default path is "C:\WINDOWS\system32\msiexec.exe"
Added a return even though its never necessary, but to make it more readable and to show you intent to return the output of the msiexec process.
Replaced \\$Computer\c$ with C:\ as there's no need to use a network connection if you are pointing to the host you are running the command in/
Hope it helps, good luck.
EDIT:
So, as you mentioned the pipeline execution gets stuck, I had this issue in the past when creating the computer preparation script for my department, what I did was use jobs to create parallel executions of the installation so if there's a computer that for some reason is slower or is just flat out stuck and never ends you can identify it, try the following as is to see how it works and then do the replaces:
#region ######## SetUp #######
$bannerInProgress = #"
#######################
#Jobs are still running
#######################
"#
$bannerDone = #"
##################################################
#DONE see results of finished installations bellow
##################################################
"#
#VARS TO SET
$MinutesToWait = 1
$computers = 1..10 | % {"qwerty"*$_} #REPLACE THIS WITH YOUR COMPUTER VALUES (Get-Content C:\Users\name\Desktop\flash.txt)
#endregion
#region ######## Main #######
#Start Jobs (REPLACE SCRIPTBLOCK OF JOB WITH YOUR INVOKE-COMMAND)
$jobs = [System.Collections.ArrayList]::new()
foreach($computer in $computers){
$jobs.Add(
(Start-Job -Name $computer -ScriptBlock {
Param(
[Parameter(Mandatory=$true, Position=0)]
[String]$computer
)
Sleep -s (Get-Random -Minimum 5 -Maximum 200)
$computer
} -ArgumentList $computer)
) | Out-Null
}
$timer = [System.Diagnostics.Stopwatch]::new()
$timer.Start()
$acceptedWait = $MinutesToWait * 60 * 1000 # mins -> sec -> millis
$running = $true
do {
cls
$jobsRunning = $jobs | Where-Object State -EQ 'Running'
if ($jobsRunning) {
Write-Host $bannerInProgress
foreach ($job in $jobsRunning) {
Write-Host "The job `"$($job.Name)`" is still running. It started at $($job.PSBeginTime)"
}
Sleep -s 3
} else {
$running = $false
}
if($timer.ElapsedMilliseconds -ge $acceptedWait){
$timer.Stop()
Write-Host "Accepted time was reached, stopping all jobs still pending." -BackgroundColor Red
$failed = #()
foreach($job in $jobsRunning){
$output = $job | Receive-Job
$failed += [PsCustomObject]#{
"ComputerName" = $job.Name;
"Output" = $output;
}
$job | Remove-Job -Force
$jobs.Remove($job)
}
$failed | Export-Csv .\pendingInstallations.csv -NoTypeInformation -Force
$running = $false
}
}while($running)
Write-host $bannerDone
$results = #()
foreach($job in $jobs){
$output = $job | Receive-Job
$results += [PsCustomObject]#{
"ComputerName" = $job.Name;
"Output" = $output;
}
}
$results | Export-Csv .\install.csv -NoTypeInformation -Force
#endregion
This script will trigger 10 jobs that only wait and return its names, then the jobs that got completed in the time that you set are consider correct and the ones that didn't are consider as pending, both groups get exported to a CSVfor review. You will need to replace the following to work as you intended:
Add $params = '/i <path to AcroPro.msi> LANG_LIST=en_US TRANSFORMS="1033.mst" /qb' in the SetUp region
Replace the declaration of $computers with $computers = Get-Content C:\Users\name\Desktop\flash.txt
Replace the body of Start-Job scriptblock with Invoke-command from thew first snippet of code in this answer.
you should end-up with something like:
.
.code
.
$params = '/i <path to AcroPro.msi> LANG_LIST=en_US TRANSFORMS="1033.mst" /qb'
#VARS TO SET
$MinutesToWait = 1
$computers = Get-Content C:\Users\name\Desktop\flash.txt
#endregion
#region ######## Main #######
#Start Jobs
$jobs = [System.Collections.ArrayList]::new()
foreach($computer in $computers){
$jobs.Add(
(Start-Job -Name $computer -ScriptBlock {
Param(
[Parameter(Mandatory=$true, Position=0)]
[String]$computer
)
Invoke-Command -ScriptBlock {
Param(
[Parameter(Mandatory=$true,Position=0)]
[String]$arguments
)
return Start-Process msiexec.exe -ArgumentList $arguments -Wait -PassThru
} -ComputerName $computer -ArgumentList $params
} -ArgumentList $computer)
) | Out-Null
}
.
. code
.
I know it looks like a complete mess, but it works.
Hope it helps.
Please help to get Invoke-Command working. It says -ScriptBlock parameter is null. It seems RegHomePage() function is not available in InlineScript{}.
function RegHomePage (){
get-item -path Registry::"HKEY_USERS\*\Software\Microsoft\Internet Explorer\Main" | `
Get-ItemProperty | Where-Object {$_."Start Page" -ne $null} | Set-ItemProperty -Name "Start Page" -Value "about:blank"
}
$creds = Get-Credential -Credential value\wadmin
workflow foreachtest
{
param([Parameter(mandatory=$true)][string[]]$computers)
foreach -parallel -throttlelimit 20 ($computer in $computers)
{
sequence
{
$isPing = Test-Connection -count 1 $computer -quiet
if($isPing){
$isWSMan = [bool](Test-WSMan $computer -ErrorAction SilentlyContinue)
}
if($isWSMan){
InlineScript{
Invoke-Command -ComputerName $USING:computer -ScriptBlock ${function:RegHomePage}
} -PSComputerName $computer
echo "$computer OK"
}
Else{
$Workflow:out += "$computer`r`n"
echo "$computer FAILED"
}
}
}
Out-File .\offline.txt -InputObject $out
}
foreachtest -computers (Get-Content .\comps.txt)
Seems to have a few issues with this inlineScript block.
Dont provide the PSComputerName parameter since you are already running a job on each computer. There is no need to reference other systems here.
I would suggest using Write-Output instead of echo (use powershell native commands)
Move the function within the inlinescript to bring it in scope of each iteration.
workflow testing {
foreach -parallel ($computer in $computers) {
sequence {
inlinescript {
function RegHomePage {
Get-Item -path Registry::"HKEY_USERS\*\Software\Microsoft\Internet Explorer\Main" | `
Get-ItemProperty | Where-Object {$_."Start Page" -ne $null} | Set-ItemProperty -Name "Start Page" -Value "about:blank"
}
Invoke-Command -ComputerName $using:computer -ScriptBlock ${Function:RegHomePage}
}
}
}
}
Following is what I tested with.
workflow testingWF {
Param ([string[]] $computers)
foreach -parallel ($computer in $computers) {
sequence {
InlineScript {
function testFunc {
Param($comp)
Write-Output "$($comp.split('.')[0]) == TestFunc"
}
Invoke-Command -ComputerName $Using:computer -ScriptBlock ${Function:testFunc} -ArgumentList $using:computer
}
}
}
}
testingWF serverFQDN1,serverFQDN2
#Prints
server1 == TestFunc
server2 == TestFunc
Suggestion on how to re-write the above code
Instead of using a workflow to run a parallel foreach loop, i would recommend replacing the functionality with -AsJob.
foreach($computer in $computers) {
Invoke-Command -ComputerName $computer -ScriptBlock ${Function:RegHomePage} -AsJob
}
# Remove Jobs when done
Get-Job | Wait-Job | Remove-Job
InlineScript dont support $using:function , try nested workflow nested work
You can move your function inside InlineScript block .
Are you sure that key -PSComputerName must have value $Computers instead $computer
Adding
Only one way to call function at inlinescriptblock, it's a put it inside. But may you can use nested workflow to call few times invoke comand. Example nested:
workflow Test-Workflow {
function mess{"get ready"}
workflow nest-test{
mess
}
nest-test
}
Test-Workflow
You can also read why you can't use import it to inline script in this tutorial:
tutorial
I am working on a Powershell script with a function and a Workflow. Unfortunately, I was unable to access variables inside the function. Here is an example :
$location = "c:\temp"
function PingComputer
{
Param($ip)
$res = Test-Connection -ComputerName $ip -quiet -Count 1
If ($res -eq "true")
{
Try
{
#Some tasks if pings are ok
#For example : copy-item -path $location -destination $dest -force -recurse
}
Catch
{
#Catch exceptions
}
}
Else
{
#Ping fail
}
}
workflow parallelPingCOmputer {
Param($ips)
$i=0
foreach -parallel($ip in $ips)
{
PingComputer($ip)
$workflow:i++
$count = $ips.Count
InlineScript {
#write-host "$using:i : " $using:ips.count " : $using:ips "
Write-Progress -Activity "Poste : $using:ip" -Status "Postes effectués : $using:i sur $using:count" -PercentComplete (($using:i / $using:Count) * 100)
sleep -s 1
}
}
}
$request = parallelPingComputer -ips $ip_list | Select-object date, computer, result | out-gridview
This is a simplified version of my current script. But, as you can see, the variable $location can't be accessed inside my function PingComputer. I tried to modify its scope as global or script, but nothing works.
The message I get with the copy-item is "path is null"... How can I make my variable accessible ?
If you want to reuse the function, just copy the function inside the workflow and keep it outside. Else, copy the function inside the workflow and remove the one outside like the code below. It could solve your problem without using a function inside the workflow.
I made an example on my Github :
Workflow Get-Ping{
Param(
[Parameter(Mandatory = $true)][string[]]$Computers
)
Foreach -Parallel ($computer in $Computers){
$ping = $null
$version = $null
if(Test-Connection -ComputerName $computer -Count 1 -Quiet){
$ping = "Online"
$version = Get-WmiObject -Namespace "root\cimv2" -Class "Win32_OperatingSystem" -PSComputerName $computer | select Version
}
else{
$ping = "Offline"
}
#if no gwmi use -ComputerName $computer
$arrayResults = New-Object -Type PSObject -Property #{
Hostname = $computer
Ping = $ping
Version = $version.Version
}
return($arrayResults)
}
}
$computers = Get-Content ".\Computers.txt"
Write-Host "$($computers.Count) computers found" -ForegroundColor Green
Get-Ping -Computers $computers | Select-Object Hostname, Ping, Version | Sort-Object Hostname | Out-GridView -Title "Powershell Workflow - Ping"