This is my first time using the workflow, could anyone explain me what is wrong with the code ?
Powershell version is 5.1
$Script = {
return(Get-Service WINRM).Status
}
workflow pushupdate{
##Select OUs
$OUs=
"OU=Workstations,DC=contoso,DC=com",
"OU=Notebooks,DC=contoso,DC=com"
foreach -parallel ($computer in ($Ous | foreach { Get-ADComputer -Filter {enabled -eq $true} -SearchBase $_} | Select Name)) {
if ((Test-Connection $computer.name -Quiet) -eq "True") {
Write-Output "Running update on:" $computer.name
InlineScript {
Invoke-Command -ComputerName $computer.name -Script $Script -Verbose
}
}
else{
Write-Output $computer.name "unreachable!"
}
}
}
pushupdate
I keep getting the error:
Invoke-Command : Cannot validate argument on parameter 'ScriptBlock'. The argument is null. Provide a valid value for
the argument, and then try running the command again.
At pushupdate:245 char:245
Variables defined outside the InlineScript block are unknown to the Invoke-Command cmdlet unless you use them as $using:<varname>.
It seems however that you cannot do that with a variable which is actually a scriptblock. That needs to be defined inside the InlineScript itself:
workflow pushupdate{
# Select OUs
$OUs = "OU=Workstations,DC=contoso,DC=com", "OU=Notebooks,DC=contoso,DC=com"
# get a string array of computerNames
$computers = ( $Ous | ForEach-Object { Get-ADComputer -Filter "Enabled -eq 'True'" -SearchBase $_ } ).Name
foreach -parallel ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Quiet -Count 1) {
Write-Output "Running update on:" $computer
InlineScript {
# define the scriptblock here
$script = {(Get-Service WINRM).Status}
Invoke-Command -ComputerName $using:computer -ScriptBlock $Script -Verbose
}
}
else{
Write-Output "$computer unreachable!"
}
}
}
pushupdate
Related
The ForEach loop on this powershell script is failing to run more than one item before dropping out?
Can someone help me on this one?
function Get-RemoteLogonStatus {
[CmdletBinding()]
param(
[string]$ComputerName = ' '
)
ForEach ($line in Get-Content C:\ADComputers.csv)
{
$Computername = $line
if ( Test-Connection -ComputerName $ComputerName -Count 3 -Quiet ) {
try {
Get-WmiObject –ComputerName $ComputerName –Class Win32_ComputerSystem | Select-Object UserName = $lname -ErrorAction Stop | Out-Null
}
catch {
Write-Output 'No user logged in - RESTARTING.'
Shutdown /r /t 0 /M \\$ComputerName
$ComputerName
return
}
Write-Output 'Computer in use.'
$ComputerName
}
else {
Write-Output 'Computer in Use or is Offline.'
$ComputerName
}
}
$error.clear
}
Get-RemoteLogonStatus
Should run more than one item from the file. The file has 4 items for test:
a function is supposed to contain a block of code that can be repeated a number of times. Your function does all in one go, hence I don't see the need for it. Also it has the possibility to take one argument, but you don't pass it.
'return' is not necessary in PowerShell, it will throw the content of a variable without the need for a 'return'.
Select-Object needs a name that is being passed from the pipe, and not an assignment.
inside the try statement you might want to get an output, but if you pipe the line to Out-null you get nothing. and the catch will never grab any error.
the write-output are not clearly positioned, and difficult to understand.
I can infer what you are trying to achieve is: reboot computers in the csv file IF no user is logged in, is that so? In that case it's much simpler:
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
of if you want to stick to a function and see its purpose see this:
function Restart-Node {
param(
[Parameter(Mandatory=$true)][string]$ComputerName
)
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
Restart-Node -ComputerName $ComputerName
}
I am trying to use the script below to test the trust relationship with the domain controller for every computer in AD. I am using powershell 2.0. When I test the script I get no output. It is based off a powershell 4.0 script that works.
$localCredential = Get-Credential
ForEach ($Name in Get-AdComputer -Filter *){
$output = { $Name = $_.Name }
if (-not (Test-Connection $Name $_.Name -Quiet -Count 1)) { $output.Status = 'Offline'
} else {
$trustStatus = Invoke-Command $Name $_.Name -ScriptBlock { Test-ComputerSecureChannel } -Credential $localCredential
$output.Status = $trustStatus
}
[pscustomobject]$output
}
Below is a powershell 4.0 script that I have tried to convert because the .ForEach syntax is not valid in Powershell 2.0.
Source: https://adamtheautomator.com/trust-relationship-between-this-workstation-and-the-primary-domain-failed/
here is the working script I tried to convert from:
$localCredential = Get-Credential
#(Get-AdComputer -Filter *).foreach({
$output = #{ ComputerName = $_.Name }
if (-not (Test-Connection -ComputerName $_.Name -Quiet -Count 1)) { $output.Status = 'Offline'
} else {
$trustStatus = Invoke-Command -ComputerName $_.Name -ScriptBlock { Test-ComputerSecureChannel } -Credential $localCredential
$output.Status = $trustStatus
}
[pscustomobject]$output
})
Does anyone know why I am not getting an output? Is there something clearly wrong with the first script I posted? Any help would be greatly appreciated.
Thank you very much,
Dave
In the foreach() statement you declare the iterator variable $Name, but inside the loop body you inconsistently use $_ as well.
You're also using [pscustomobject]#{}, a special object allocation syntax that was introduced in PowerShell 3.0 - you need to use New-Object psobject -Property in version 2.0.
Finally, your $output variable needs to be a dictionary and not a scriptblock (notice the # in front of { Name = ... }).
To fix it all:
ForEach ($Computer in Get-AdComputer -Filter *){
$output = #{ Name = $Computer.Name }
if (-not (Test-Connection $Computer.Name -Quiet -Count 1)) {
$output.Status = 'Offline'
} else {
$trustStatus = Invoke-Command -ComputerName $Computer.Name -ScriptBlock { Test-ComputerSecureChannel } -Credential $localCredential
$output.Status = $trustStatus
}
New-Object psobject -Property $output
}
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
It seems to be strange but I can't assign a value to variable inside of Invoke-Command. Here is the code below but when print out $targetComputerPath it's simply empty. What's wrong?
foreach ($item in $computersPath){
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue)
{
if ($((Get-Service WinRM -ComputerName $computername).Status) -eq "stopped")
{
(Get-Service WinRM -ComputerName $computername).Start()
}
Invoke-Command -ComputerName $computername -ScriptBlock {
if ($((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId) -eq "1903" )
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "1903"
}
else
{
$targetComputerPath = "\\"+$computername+"\c$\Users\"+$username+"\Desktop\"
write-host "something else"
}
}
}
write-host $targetComputerPath
}
The point of WinRM is that you take a script block, and execute it on a different machine.
None of the variables you define in the host script will be available on the remote machine.
This becomes more apparent when you separate the "task", a.k.a the script block, from the Invoke-Command, like this:
$task = {
$version = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
if ($version.ReleaseId -eq "1903") {
# note that `$username` cannot be available here, it's never been defined!
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
} else {
return "\\$env:COMPUTERNAME\c$\Users\$username\Desktop"
}
}
foreach ($item in $computersPath) {
$computername = $item.Name
$username = $item.UserID
Write-Host computer $computername and user $username
if (Test-Connection -ComputerName $computername -Count 1 -ErrorAction SilentlyContinue) {
$winrm = Get-Service WinRM -ComputerName $computername
if ($winrm.Status -eq "stopped") { $winrm.Start() }
$targetComputerPath = Invoke-Command -ComputerName $computername -ScriptBlock $task
Write-Host "The machine returned: $targetComputerPath"
}
}
As you can see, you can return values from the script block and they will be available as the return value of Invoke-Command.
If you want to pass arguments to your script block, this thread talks about that: How do I pass named parameters with Invoke-Command?
The code below turns off firewall on each remote computers and return any computers that was turned off. I am also trying to retrieve software that has been authorized to pass through firewall for each computer.
I understand that I am using try, catch so is there any way to print the output of $Appfilter to offComp&programsALLO.txt ? The text file just prints the value of $Appfilter.
The output should ideally look like:
Computers:
"name of computer" followed by "programs allowed"
Here is the code:
Get-ADComputer -Filter * | Select-Object -ExpandProperty Name | Out-File .\ADcomputers.txt
$LaunchLine = 'powershell.exe -Version 4.0 -Command "& {netsh advfirewall set allprofiles state off}"'
$Appfilter = 'powershell.exe -Version 4.0 -Command "& {Get-NetFirewallApplicationFilter -program * | fl program}"'
$ComputerList = Get-Content .\adcomputers.txt
foreach($Computer in $ComputerList) {
[String]$wmiPath = "\\{0}\root\cimv2:win32_process" -f $computer
try {
[wmiclass]$Executor = $wmiPath
$executor.Create($LaunchLine, $Appfilter)
} catch {
Add-Content offComp&programsALLO.txt "computers:$Computer, $Appfilter "
}
}
I would use Invoke-Command with the -ComputerName parameter if possible:
#store AD Computer names in an array
$computerList = (Get-ADComputer -Filter *).Name
#declare results arrays
$results = #()
$offline = #()
#for each computer
foreach($computer in $computerList) {
#if computer responds to ping
if(Test-Connection $computer -Count 2 -Quiet -ErrorAction SilentlyContinue) {
#disable firewall
Invoke-Command -ComputerName $computer -ScriptBlock {
netsh advfirewall set allprofiles state off
} | Out-Null
#store retrieved authorized programs list in an array
$programs = Invoke-Command -ComputerName $computer -ScriptBlock {
(Get-NetFirewallApplicationFilter).Program
}
#build results object and add it to results array
$results += [PSCustomObject]#{
ComputerName = $computer
Programs = $programs -join ";"
}
} else {
#build results object and add it to offline array
$offline += [PSCustomObject]#{
ComputerName = $computer
Status = "OFFLINE"
}
}
}
#export results to files
$results | Out-File "report.txt"
$offline | Out-File "offline.txt"