Ron Shorts
asked on
Powershell script, help with for loop and running sccm advertisement remotely
Hi Experts,
I've been working on this powershell script and need some help with a couple of things. The script will be called from this vbscript to pass the computername or listofcomputers variables.
What I'd like expertise or help with is:
1. I've tested calling this from the VBScript Code in top comments section, but didn't get the computername variable to pass down. (this would be done from the vbscript above, but wanted just a test wrapper to test). EDIT: If this is too difficult, I'd like to set it up to pull from a list of computers.
2. I'm first running "Test-MyConnection" (if) the computer is online, I'm then calling a executable.exe. This is the part I'm having issues with - I don't necessarily want to run a .exe here, but call an SCCM advert (pasted below from client center), or, I also have a vbscript that runs the advert I could call out to. This advert would exist locally on every computer. The part I'm unsure about, or how to code is having this advert run on each computer in the list of variables
SCCM Advert Code:
Any help is greatly appreciated!
I've been working on this powershell script and need some help with a couple of things. The script will be called from this vbscript to pass the computername or listofcomputers variables.
What I'd like expertise or help with is:
1. I've tested calling this from the VBScript Code in top comments section, but didn't get the computername variable to pass down. (this would be done from the vbscript above, but wanted just a test wrapper to test). EDIT: If this is too difficult, I'd like to set it up to pull from a list of computers.
2. I'm first running "Test-MyConnection" (if) the computer is online, I'm then calling a executable.exe. This is the part I'm having issues with - I don't necessarily want to run a .exe here, but call an SCCM advert (pasted below from client center), or, I also have a vbscript that runs the advert I could call out to. This advert would exist locally on every computer. The part I'm unsure about, or how to code is having this advert run on each computer in the list of variables
SCCM Advert Code:
get-wmiobject -query "SELECT * FROM CCM_Scheduler_ScheduledMessage WHERE ScheduledMessageID='SCCM00001-SCCM00002-5D2960FE'" -namespace "ROOT\ccm\policy\machine\actualconfig"
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_RepeatRunBehavior='RerunAlways';$a.Put()
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_RepeatRunBehavior='RerunAlways';$a.Put()
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_MandatoryAssignments=$True;$a.Put()
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_MandatoryAssignments=$True;$a.Put()
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.PRG_Requirements='<?xml version=''1.0'' ?><SWDReserved> <PackageHashVersion>4</PackageHashVersion> <PackageHash.1></PackageHash.1> <PackageHash.2>12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7428D742DC1D7885</PackageHash.2> <NewPackageHash><Hash HashPreference="4" Algorithm="140789027962884" HashString="12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7428D742DC1D7885" SignatureHash="55F8A767E82821593F124ECFA375139FE56DD213F6D80A68A8AD377AADA9EF0B"/></NewPackageHash> <ProductCode></ProductCode> <DisableMomAlerts>false</DisableMomAlerts> <RaiseMomAlertOnFailure>false</RaiseMomAlertOnFailure> <BalloonRemindersRequired>false</BalloonRemindersRequired> <PersistOnWriteFilterDevices>true</PersistOnWriteFilterDevices> <DefaultProgram>false</DefaultProgram> <PersistInCache>0</PersistInCache> <DistributeOnDemand>false</DistributeOnDemand> <Multicast>false</Multicast> <MulticastOnly>false</MulticastOnly> <MulticastEncrypt>false</MulticastEncrypt> <DonotFallback>true</DonotFallback> <PeerCaching>true</PeerCaching> <OptionalPreDownload>false</OptionalPreDownload> <PreDownloadRule></PreDownloadRule> <Requirements></Requirements> <AssignmentID></AssignmentID> <ScheduledMessageID>SCCM00001-SCCM00002-5D2960FE</ScheduledMessageID> <OverrideServiceWindows>TRUE</OverrideServiceWindows> <RebootOutsideOfServiceWindows>FALSE</RebootOutsideOfServiceWindows> <WoLEnabled>FALSE</WoLEnabled></SWDReserved>';$a.Put()
$a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.PRG_Requirements='<?xml version=''1.0'' ?><SWDReserved> <PackageHashVersion>4</PackageHashVersion> <PackageHash.1></PackageHash.1> <PackageHash.2>12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7442DC1D7885</PackageHash.2> <NewPackageHash><Hash HashPreference="4" Algorithm="140789027962884" HashString="1249725F9E9FBCB1249D5D3CB463912A25ETGAF5BF9FA7A6D7428D742DC1D7885" SignatureHash="55F8A767E8282154ECFA375139FE56DD213F6D80A68A8AD377AADA9EF0B"/></NewPackageHash> <ProductCode></ProductCode> <DisableMomAlerts>false</DisableMomAlerts> <RaiseMomAlertOnFailure>false</RaiseMomAlertOnFailure> <BalloonRemindersRequired>false</BalloonRemindersRequired> <PersistOnWriteFilterDevices>true</PersistOnWriteFilterDevices> <DefaultProgram>false</DefaultProgram> <PersistInCache>0</PersistInCache> <DistributeOnDemand>false</DistributeOnDemand> <Multicast>false</Multicast> <MulticastOnly>false</MulticastOnly> <MulticastEncrypt>false</MulticastEncrypt> <DonotFallback>true</DonotFallback> <PeerCaching>true</PeerCaching> <OptionalPreDownload>false</OptionalPreDownload> <PreDownloadRule></PreDownloadRule> <Requirements></Requirements> <AssignmentID></AssignmentID> <ScheduledMessageID>SCCM00001-SCCM00002-5D29FE</ScheduledMessageID> <OverrideServiceWindows>TRUE</OverrideServiceWindows> <RebootOutsideOfServiceWindows>FALSE</RebootOutsideOfServiceWindows> <WoLEnabled>FALSE</WoLEnabled></SWDReserved>';$a.Put()
([wmiclass]'ROOT\ccm:SMS_Client').TriggerSchedule('SCCM00001-SCCM00002-5D2960FE')
Any help is greatly appreciated!
ASKER
Thanks for look at it Michael, instead of calling it from the vbscript and passing the list, could you help me set it up to pull from a list of computers just within the powershelll script?
Any examples you can point me to for running the SCCM advertisement remotely from the list?
Any examples you can point me to for running the SCCM advertisement remotely from the list?
After a short glance at the VB script: It's main purpose seems to be to get parameter values for your PowerShell script from a database.
This is also easily done in PowerShell.
Thus you should migrate your VB script to PowerShell. Cause then you can hand over arrays as parameter or use pipelining.
This is also easily done in PowerShell.
Thus you should migrate your VB script to PowerShell. Cause then you can hand over arrays as parameter or use pipelining.
Nice script to remotely rerun an advertisement for a package or application:
http://www.christopherkibb le.com/usi ng-powersh ell-rerun- sccm-clien t-advertis ement/
You can wrap this i.e. with a loop and pull the computer names from a text file:
http://www.christopherkibb
You can wrap this i.e. with a loop and pull the computer names from a text file:
$ComputerList = Get-Content .\computerlist,.txt
$ComputerList | foreach { Start-CCMRerunAdvertisement -ComputerName $_ -AdvertisementID Your AdvertisementID }
Or grab the computer list from a collection:
Get-CMDevice -CollectionName <String> | foreach ….
Get-CMDevice -CollectionName <String> | foreach ….
ASKER
Michael, in pulling from a list, I would want it to grab the list of each computer the run through the entire sequence:
1. Function Test-Myconnection to test connectivity, then run the advertisement
2. The move to Test-Running, where it checks the processes, then when this exits, issue the restart-computer command
Questions:
Where would I set this up in the script?
Would this cycle through each computer in the list and wait until finished before moving to the next computer? If so, how could I set this up to kick off the sequence for the first computer, then move on to the rest of the computers in the list?
I'm getting this error with the code you have, any ideas?
Thank you
1. Function Test-Myconnection to test connectivity, then run the advertisement
2. The move to Test-Running, where it checks the processes, then when this exits, issue the restart-computer command
Questions:
Where would I set this up in the script?
Would this cycle through each computer in the list and wait until finished before moving to the next computer? If so, how could I set this up to kick off the sequence for the first computer, then move on to the rest of the computers in the list?
I'm getting this error with the code you have, any ideas?
Start-CCMRerunAdvertisement : The term 'Start-CCMRerunAdvertiseme nt' 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.
At c:\test.ps1:52 char:27
+ $ComputerList | foreach { Start-CCMRerunAdvertisement -ComputerName $_ -Advertis ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Start-CCMRerunAdvertisement:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Thank you
Can't test here right now but this should give you the basic idea:
Put the linked PowerShell Code at the beginning of your script
Then run
foreachrerun <list of computers>
HTH
Put the linked PowerShell Code at the beginning of your script
Function Start-CCMRerunAdvertisement { …
:
}
workflow foreachrerun {
param([string[]]$computers)
foreach –parallel ($computer in $computers) {
Start-CCMRerunAdvertisement –ComputerName $Computer -AdvertisementID ….
}
}
Then run
foreachrerun <list of computers>
HTH
ASKER
Michael, when you have time, would you mind looking at what I have here to check if this looks right? https://pastebin.com/0niiTKUE
Thanks, Ron
Thanks, Ron
<#
*******************************************************************************************************************************
** All code is for demonstration only and should be used at your own risk. I cannot accept liability for unexpected results. **
*******************************************************************************************************************************
Use: You're welcome to use, modify, and distribute this script. I'd love to hear about how you're using it or
modifications you've made in the comments section of the original post over at ChristopherKibble.com.
#>
Function Start-CCMRerunAdvertisement {
<#
.SYNOPSIS
Restarts an SCCM advertisement on a remote computer.
.DESCRIPTION
This script will remotely connect to a computer running the CCM Client,
find an advertisement by the Advertisement ID or the Package ID (or
both), and then restart the advertisement.
.NOTES
File Name : Start-CCMRerunAdvertisement.ps1
Version : 2017-09-21 (v1.0)
Author : Christopher Kibble (www.ChristopherKibble.com)
Tested : PowerShell Version 5
.PARAMETER ComputerName
The name of the remote computer to connect to.
.PARAMETER AdvertisementID
All or part of the Advertisement ID to run. Wildcards accepted.
Defaults to *. Either this or PackageID must be specified.
.PARAMETER PackageID
All or part of the Package ID to run. Wildcards accepted.
Defaults to *. Either this or AdvertisementID must be specified.
.PARAMETER MaxRun
If more than one advertisement meets your criteria, how many of the
advertisements to run. Defaults to 1.
.PARAMETER MoreThanPing
In environments where ICMP may not allow pinging a remote computer,
this switch will make the script attempt to connect to C$ on the remote
computer in order to determine if it's online.
.EXAMPLE
Start-CCMRerunAdvertisement -ComputerName SANDIAGO-001 -AdvertisementID "US000001"
#>
[CmdLetBinding()]Param(
[Parameter(Mandatory=$true)][string]$computerName,
[Parameter(Mandatory=$false)][string]$advertisementId = "*",
[Parameter(Mandatory=$false)][string]$packageId = "*",
[Parameter(Mandatory=$false)][int]$maxRun = 1,
[Parameters(Mandatory=$false)][switch]$moreThanPing = $false
)
# TODO LIST:
#
# - Better error control when WMI connections fail.
# - Are we using the best method to sort when using MaxRun?
#
if($advertisementId -eq "*" -and $packageId -eq "*") {
Write-Error "You must supply either an AdvertisementID or a PackageID"
return "Missing Parameters"
break
}
$searchString = "$advertisementId-$packageId-*"
if(!(Test-Connection -ComputerName $computername -ErrorAction SilentlyContinue)) {
if($moreThanPing) {
if(!(Get-ChildItem "\\$computername\c$" -ErrorAction SilentlyContinue)) {
Write-Error "System Offline"
Return "System Offline"
break
}
} else {
Return "System Offline"
break
}
}
Write-Verbose "Getting ID of ScheduleMessage on $computername"
$schMsgs = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_Scheduler_ScheduledMessage
$thisMsg = $schMsgs | ? { $_.ScheduledMessageID -like $searchString } | Sort ActiveTime -Descending | select -First $maxRun
if(!$thisMsg) {
Write-Verbose "Cannot Find Advertisement/Package on Target Computer"
Return "Cannot Find Advertisment"
break
}
$thisMsg | % {
[xml]$activeMessage = $_.activeMessage
$amProgramId = $activeMessage.SoftwareDeploymentMessage.ProgramID
$amAdvId = $activeMessage.SoftwareDeploymentMessage.AdvertisementID
$amPkgId = $activeMessage.SoftwareDeploymentMessage.PackageID
$ScheduledMessageId = $_.ScheduledMessageId
Write-Verbose "Restarting $amArogramId (ADV=$amAdvId) (PKG=$amPkgId) for Schedule Message $ScheduledMessageId"
$softwareDist = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_SoftwareDistribution -Filter "ADV_AdvertisementID = '$amAdvId' and PKG_PackageID = '$amPkgId'"
$original_Rerun = $softwareDist.ADV_RepeatRunBehavior
if($original_Rerun -ne "RerunAlways") {
write-verbose "Changing Rerun Status from $original_Rerun to RerunAlways"
$softwareDist.ADV_RepeatRunBehavior = "RerunAlways"
$softwareDist.put() | Out-Null
}
Write-Verbose "Triggering Schedule on $computername"
Invoke-WmiMethod -ComputerName $computername -Namespace "root\ccm" -Class "SMS_CLIENT" -Name TriggerSchedule $ScheduledMessageId | Out-Null
Write-Verbose "Sleeping for 5 seconds"
Start-Sleep -Seconds 5
if($original_Rerun -ne "RerunAlways") {
Write-Verbose "Changing Rerun Status back to $original_Rerun"
$softwareDist.ADV_RepeatRunBehavior = "$original_Rerun"
$softwareDist.put() | Out-Null
}
Return "Reran Advertisement"
}
}
function Send-Email {
# param (
[Parameter(Mandatory=$true)]
[String]$Body,
[String]$ComputerName
# )
Send-MailMessage -From “admin@domain.com" -To “user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is OFFLINE " # -Body $Body
}
function Test-Running {
param (
[Parameter(Mandatory=$true)]
[System.String]$ComputerName
)
$processList = @'
"Name", "Expected", "Running"
"cmd", "1", "0"
"powershell", "1", "0"
"OfficeClickToRun", "2", "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
$splat = @{}
$splat['ComputerName'] = $computerName
Write-Host "Testing processes on $($computerName)" -ForegroundColor Yellow
Do {
$processList | ForEach-Object {
$_.Running = @(Get-Process $_.Name @splat -ErrorAction SilentlyContinue).Count
}
($processList | Format-Table -AutoSize | Out-String).Split("`r`n", [StringSplitOptions]::RemoveEmptyEntries) | Write-Host
If ($running = @($processList | Where-Object {$_.Running -ge $_.Expected}).Count) {
Start-Sleep -Seconds 5
}
} Until (-not $running)
Restart-Computer -Computer $Computername -Force
return 0
}
function Test-MyConnection {
param (
[Parameter(Mandatory=$true)]
[System.String]
$ComputerName
)
if (Test-Connection $computername -Quiet -Count 2) {
Write-Host "Ping successful on $($computerName)" -ForegroundColor Green
Test-Running -ComputerName $ComputerName
}
else {
send-email $computername -ComputerName $computername -body $processlistOP
Write-Host $($computerName) "is Offline." -ForegroundColor Red
}
}
workflow foreachrerun {
param([string[]]$computers)
foreach –parallel ($computer in $computers) {
Start-CCMRerunAdvertisement –ComputerName $Computer -AdvertisementID UNC53487
}
}
$ComputerList = Get-Content "c:\computerlist.txt"
foreachrerun -Computers $ComputerList
I didn't use the other functions like Test-Connection. This has not been tested, currently I have no access to a System Center environment.
ASKER
@Michael - I ran the sccm powershell script from the site, and got the same error. I found this one which worked for me when I put in the remote computer name: https://marco-difeo.de/2011/10/13/powershell-sccm-readvertise-a-previously-installed-softwarepackage-remotly/
I put this into a script here: https://pastebin.com/52LWwZZH
Here's what I need help with if this can be done:
Putting the first part of the script (lines 1 - 111) into a function. Then run this at (line 164) in the sequence.
Build a for loop to pull computer names from a text file to cycle through (currently line 15 just for one computer).
Run the other functions and sequence in this order from the list of computers:
a) Test-MyConnection (line 155)
b) Function from step 1 above (SCCM advertisement)
c) Test-Running (already built into Test-MyConnection function) to monitor processes count.
Also wanted to see if running a list like this, it would need to wait for each computer to finish, or it could execute the functions and move to the next.
Many thanks!
I put this into a script here: https://pastebin.com/52LWwZZH
Here's what I need help with if this can be done:
Putting the first part of the script (lines 1 - 111) into a function. Then run this at (line 164) in the sequence.
Build a for loop to pull computer names from a text file to cycle through (currently line 15 just for one computer).
Run the other functions and sequence in this order from the list of computers:
a) Test-MyConnection (line 155)
b) Function from step 1 above (SCCM advertisement)
c) Test-Running (already built into Test-MyConnection function) to monitor processes count.
Also wanted to see if running a list like this, it would need to wait for each computer to finish, or it could execute the functions and move to the next.
Many thanks!
ASKER
Michael, if the above can't be done, I did get Christopher Kibble's script to work as a standalone... here's what I had to do...
Remark out this line: # [Parameters(Mandatory=$fal se)][switc h]$moreTha nPing = $false # This line was causing the error
Put this in the bottom of Christopher's script: Start-CCMRerunAdvertisemen t -ComputerName SANDIAGO-001 -AdvertisementID "US000001"
-or-
Ran this from another powershell shell: powershell -Command "& 'C:\Start-CCMRerunAdvertis ement.ps1' 'Start-CCMRerunAdvertiseme nt' '-ComputerName' '"SANDIAGO-001" -AdvertisementID "US000001"'"
Could you help me with the logic on this to pull from a list of computers and sequence the other functions I have please?
Remark out this line: # [Parameters(Mandatory=$fal
Put this in the bottom of Christopher's script: Start-CCMRerunAdvertisemen
-or-
Ran this from another powershell shell: powershell -Command "& 'C:\Start-CCMRerunAdvertis
Could you help me with the logic on this to pull from a list of computers and sequence the other functions I have please?
No better idea right now for parallel processing than splitting it in 2 pieces...
RerunAdvertisement.ps1 wraps all the stuff you want for a single PC. You can test it by starting .\RerunAdvertisement.ps1 "computername" for a system
To run it in parallel use this:
HTH
RerunAdvertisement.ps1 wraps all the stuff you want for a single PC. You can test it by starting .\RerunAdvertisement.ps1 "computername" for a system
To run it in parallel use this:
$ComputerList = Get-Content "c:\computerlist.txt"
foreach ($Computer in $ComputerList) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 8) {
Start-Job -filepath .\rerunadvertisement.ps1 -ArgumentList $Computer
} else {
$running | Wait-Job
}
Get-Job | Receive-Job
}
}
HTH
ASKER
Can you give an example of the command line arguments I'd need to pass? Tried this:
rerunadvertisement.ps1" Start-CCMRerunAdvertisemen t -Computername $Computer -AdvertisementID "XXX00001"
Getting this error:
Start-Job : Cannot bind parameter 'InitializationScript'. Cannot convert the "Start-CCMRerunAdvertiseme nt" value of type "System.String" to type "System.Management.Automat ion.Script Block".
It's getting hung up right where the -Argument list starts. Would I need to modify rerunadvertisement.ps1?
rerunadvertisement.ps1" Start-CCMRerunAdvertisemen
Getting this error:
Start-Job : Cannot bind parameter 'InitializationScript'. Cannot convert the "Start-CCMRerunAdvertiseme
It's getting hung up right where the -Argument list starts. Would I need to modify rerunadvertisement.ps1?
Oh great ... I didn't realize the upload isn't there .. sorry.
I'm not in the office where the original file is so I tried to remember...
Save the attachment to a folder and test it:
.\RerunAdvertisement.ps1 -ComputerName xxxx -AdvertisementID YYYZZZZ
If its ok you can use the wrapper 2 posts above, put it in a 2nd PowerShell script to run it in parallel.
HTH
RerunAdvertisement.ps1
I'm not in the office where the original file is so I tried to remember...
Save the attachment to a folder and test it:
.\RerunAdvertisement.ps1 -ComputerName xxxx -AdvertisementID YYYZZZZ
If its ok you can use the wrapper 2 posts above, put it in a 2nd PowerShell script to run it in parallel.
HTH
RerunAdvertisement.ps1
ASKER
No problem, thanks. So this works perfectly using the command line above.
The wrapper I can't seem to get to work, here is exactly what I'm using, minus the actual AdvertisementID:
The wrapper I can't seem to get to work, here is exactly what I'm using, minus the actual AdvertisementID:
$ComputerList = Get-Content "c:\computerlist.txt"
foreach ($Computer in $ComputerList) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 8) {
Start-Job -filepath .\rerunadvertisement.ps1 -ArgumentList $Computer -AdvertisementID "YYYZZZZ"
} else {
$running | Wait-Job
}
Get-Job | Receive-Job
}
Can't test here but the line should read
Start-Job -filepath .\rerunadvertisement.ps1 -ComputerName $Computer -AdvertisementID "YYYZZZZ"
ASKER
Hmmm.. that what I had, it's not recognizing the 'ComputerName' parameter?
Start-Job : A parameter cannot be found that matches parameter name 'ComputerName'.
At C:\Wrapper-RerunAdvertisem ent.ps1:6 char:57
+ Start-Job -filepath .\rerunadvertisement.ps1 -ComputerName $Computer ...
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-Job], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Mic rosoft.Pow erShell.Co mmands.Sta rtJobComma nd
Start-Job : A parameter cannot be found that matches parameter name 'ComputerName'.
At C:\Wrapper-RerunAdvertisem
+ Start-Job -filepath .\rerunadvertisement.ps1 -ComputerName $Computer ...
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-Job], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Mic
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Michael, works fantastic, much appreciated.
One other thing, as ste5an mentioned, if I want to grab an array of computer names from a vbscript like this:
https://pastebin.com/ip1RSZxC, and pass the computernames into this script instead of from a list, would this be too difficult, or would it be more efficient to just re-write the vbscript into powershell?
Thanks again
One other thing, as ste5an mentioned, if I want to grab an array of computer names from a vbscript like this:
https://pastebin.com/ip1RSZxC, and pass the computernames into this script instead of from a list, would this be too difficult, or would it be more efficient to just re-write the vbscript into powershell?
Thanks again
Sorry, must have missed your comment.
Passing an unknown number of elements is difficult
You could:
a) call the PowerShell script with the computer name as parameter from your VBScript for each computer (ok, no pararell processing here)
b) let the VBScript write the computernames to a text file and import the file to the PowerShell script
c) as you said rewrite the VBScipt in PowerShell.
In case you're choosing c) have a look at the great possibilities the SCCM PowerShell module has to offer so you don't have to struggle with direct queries to the SCCM database.
Passing an unknown number of elements is difficult
You could:
a) call the PowerShell script with the computer name as parameter from your VBScript for each computer (ok, no pararell processing here)
b) let the VBScript write the computernames to a text file and import the file to the PowerShell script
c) as you said rewrite the VBScipt in PowerShell.
In case you're choosing c) have a look at the great possibilities the SCCM PowerShell module has to offer so you don't have to struggle with direct queries to the SCCM database.
ASKER
Thanks Michael. So I did evolve this to use inline processing but I'm still having a couple of issues I was wondering if you could help with.
This works how I need it to: run the sccm advertisement on remote computers from a list and monitors the processes, then reboots.
However, this advertisement needs to be run multiple times on each computer and multiple reboots.
PROBLEM: I have this sending an email and writing to a CSV file each reboot. The emails are too much to keep track of. The CSV is unable to be viewed during the time it's being logged to.
Is there a way to have the emails sent to all of the computers in the input list only and not after each run?
Is there a way to have the logging done to the CSV without disrupting if the script is running?
Finally, not sure if this is possible, but is there a way to have this whole script process 5 times after reboots for each computer in the input computerlist.txt?
Thanks for all of your help!
This works how I need it to: run the sccm advertisement on remote computers from a list and monitors the processes, then reboots.
However, this advertisement needs to be run multiple times on each computer and multiple reboots.
PROBLEM: I have this sending an email and writing to a CSV file each reboot. The emails are too much to keep track of. The CSV is unable to be viewed during the time it's being logged to.
Is there a way to have the emails sent to all of the computers in the input list only and not after each run?
Is there a way to have the logging done to the CSV without disrupting if the script is running?
Finally, not sure if this is possible, but is there a way to have this whole script process 5 times after reboots for each computer in the input computerlist.txt?
Thanks for all of your help!
$OutArray = @()
workflow foreachrerun {
param([string[]]$computers)
foreach –parallel ($computer in $computers) {
InlineScript {
Function Start-CCMRerunAdvertisement {
[CmdLetBinding()]Param(
[Parameter(Mandatory=$true)][string]$computerName,
[Parameter(Mandatory=$false)][string]$advertisementId = "*",
[Parameter(Mandatory=$false)][string]$packageId = "*",
[Parameter(Mandatory=$false)][int]$maxRun = 1
)
if($advertisementId -eq "*" -and $packageId -eq "*") {
Write-Error "You must supply either an AdvertisementID or a PackageID"
return "Missing Parameters"
break
}
$searchString = "$advertisementId-$packageId-*"
if(!(Test-Connection -ComputerName $computername -ErrorAction SilentlyContinue)) {
if($moreThanPing) {
if(!(Get-ChildItem "\\$computername\c$" -ErrorAction SilentlyContinue)) {
Write-Error "System Offline"
Return "System Offline"
break
}
} else {
Return "$Computername is Offline"
break
}
}
Write-Verbose "Getting ID of ScheduleMessage on $computername"
$schMsgs = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_Scheduler_ScheduledMessage
$thisMsg = $schMsgs | ? { $_.ScheduledMessageID -like $searchString } | Sort ActiveTime -Descending | select -First $maxRun
if(!$thisMsg) {
Write-Verbose "Cannot Find Advertisement/Package on Target Computer"
Return "Cannot Find Advertisment"
break
}
$thisMsg | % {
[xml]$activeMessage = $_.activeMessage
$amProgramId = $activeMessage.SoftwareDeploymentMessage.ProgramID
$amAdvId = $activeMessage.SoftwareDeploymentMessage.AdvertisementID
$amPkgId = $activeMessage.SoftwareDeploymentMessage.PackageID
$ScheduledMessageId = $_.ScheduledMessageId
Write-Verbose "Restarting $amArogramId (ADV=$amAdvId) (PKG=$amPkgId) for Schedule Message $ScheduledMessageId"
$softwareDist = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_SoftwareDistribution -Filter "ADV_AdvertisementID = '$amAdvId' and PKG_PackageID = '$amPkgId'"
$original_Rerun = $softwareDist.ADV_RepeatRunBehavior
if($original_Rerun -ne "RerunAlways") {
write-verbose "Changing Rerun Status from $original_Rerun to RerunAlways"
$softwareDist.ADV_RepeatRunBehavior = "RerunAlways"
$softwareDist.put() | Out-Null
}
Write-Verbose "Triggering Schedule on $computername"
Invoke-WmiMethod -ComputerName $computername -Namespace "root\ccm" -Class "SMS_CLIENT" -Name TriggerSchedule $ScheduledMessageId | Out-Null
Write-Verbose "Sleeping for 5 seconds"
Start-Sleep -Seconds 5
if($original_Rerun -ne "RerunAlways") {
Write-Verbose "Changing Rerun Status back to $original_Rerun"
$softwareDist.ADV_RepeatRunBehavior = "$original_Rerun"
$softwareDist.put() | Out-Null
}
Return "Kickit Advert Reran on succesfully on $computername"
}
}
function Send-EmailOffline {
Send-MailMessage -From “admin@domain.com" -To "user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
}
function Send-EmailComplete {
Send-MailMessage -From “admin@domain.com" -To "user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Script completed." -Body "$($ComputerName) Script completed and is rebooting."
}
function Test-MyConnection {
param (
[Parameter(Mandatory=$true)]
[System.String]
$ComputerName
)
if (Test-Connection $computername -Quiet -Count 2) {
Write-Output "Ping successful on $($computerName)"
Test-Running -ComputerName $ComputerName
}
else {
Send-EmailOffline -ComputerName $computername
}
}
function Test-Running {
param (
[Parameter(Mandatory=$true)]
[System.String]$ComputerName
)
$processList = @'
"Name", "Expected", "Running"
"cmd", "1", "0"
"powershell", "1", "0"
"OfficeClickToRun", "2", "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
$splat = @{}
$splat['ComputerName'] = $computerName
Do {
$processList | ForEach-Object {
$_.Running = @(Get-Process $_.Name @splat -ErrorAction SilentlyContinue).Count
}
($processList | Format-Table -AutoSize | Out-String).Split("`r`n", [StringSplitOptions]::RemoveEmptyEntries) | Write-Output
If ($running = @($processList | Where-Object {$_.Running -ge $_.Expected}).Count) {
Start-Sleep -Seconds 5
}
Write-Output "Monitoring processes on $($computerName)"
} Until (-not $running)
Send-EmailComplete -ComputerName $computername
# Array for logging
$Today = (Get-Date).ToString('MM-dd-yyyy'),
$ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
$Logfile = "C:\Temp\Script.csv"
$myobj = "" | Select "Computer", "ExactTime"
$myobj.Computer = $ComputerName
$myobj.ExactTime = $($(Get-Date).ToString('yyyy-MM-dd::hh:mm:ss'))
$OutArray += $myobj
$OutArray | Export-Csv $Logfile -NoTypeInformation -Append
Restart-Computer -Computer $Computername -Force
Return "Process count finished on $computername"
}
Start-CCMRerunAdvertisement –ComputerName $using:Computer -AdvertisementID "XXX00000"
Test-MyConnection -ComputerName $using:Computer
}
}
}
$ComputerList = Get-Content "C:\Temp\computerlist.txt"
foreachrerun -Computers $ComputerList
I have to ask: why isn't SCCM doing all this and you need an extra script?
ASKER
Sure, this is for new builds, and to have less admin work through the client center to make sure all updates are installed. The advertisement I'm running is a call to a local sccm package that triggers the schedule for all updates, scans, software assignments, etc., This installs updates, software for each, the reason for multiple reboots is due to cumulative updates during the process, or updates after software installations, etc.,
I usually put all that stuff in the task sequence for the OS deployment. Updates, reboots, etc.
When the task sequence ends the client is up to date.
And a cumulative update is a cumulative update. No need to install the older ones...
When the task sequence ends the client is up to date.
And a cumulative update is a cumulative update. No need to install the older ones...
Writing everything to a temporary text file and importing it back in PowerShell might be a workable solution.
I've looked at the VBScript and it does a lot with direct SCCM SQL queries… I'd rather dig in to the PowerShell module for SCCM.
It can very likely doing everything you want to achive with just one script.