I’ve adopted this script to automate SCCM client repairs. All i’m trying to do is add a simple Out-File to capture names of any successful / failed devices. What i have right now doesn’t seem to create the file
###########################################################
# AUTHOR : Marius / Hican - http://www.hican.nl - @hicannl
# DATE : 07-05-2012
# COMMENT : Automated remote repair of SCCM client on a
# list of machines, based on an input file.
###########################################################
#ERROR REPORTING ALL
Set-StrictMode -Version latest
Set-ExecutionPolicy bypass
#----------------------------------------------------------
#STATIC VARIABLES
#----------------------------------------------------------
$SCRIPT_PARENT = Split-Path -Parent $MyInvocation.MyCommand.Definition
#----------------------------------------------------------
#FUNCTION RepairSCCM
#----------------------------------------------------------
Function Repair_SCCM
{
Write-Host "[INFO] Get the list of computers from the input file and store it in an array."
$arrComputer = Get-Content ($SCRIPT_PARENT + "\input.txt")
Foreach ($strComputer In $arrComputer)
{
#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
If ($strComputer.substring(0,1) -ne "*")
{
Write-Host "[INFO] Starting trigger script for $strComputer."
Try
{
$getProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
If ($getProcess)
{
Write-Host "[WARNING] SCCM Repair is already running. Script will end."
Exit 1
}
Else
{
Write-Host "[INFO] Connect to the WMI Namespace on $strComputer."
$SMSCli = [wmiclass] $SMSCli = [wmiclass] "\\$strComputer\root\ccm:sms_client"
Write-Host "[INFO] Trigger the SCCM Repair on $strComputer."
# The actual repair is put in a variable, to trap unwanted output.
$repair = $SMSCli.RepairClient()
Write-Host "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $strComputer."
########## START - PROCESS / PROGRESS CHECK AND RUN
# Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
Write-Host "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
For ($i = 0; $i -le 470; $i++)
{
$checkProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
Start-Sleep 1
Write-Progress -Activity "Repairing client $strComputer ..." -Status "Repair running for $i seconds ..."
If ($checkProcess -eq $Null)
{
Write-Host "[INFO] SCCM Client repair ran for $i seconds."
Write-Host "[INFO] SCCM Client repair process ran successfully on $strComputer."
Write-Host "[INFO] Check \\$strComputer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
$strComputer | Out-File -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairresults.txt" -NoClobber -Append
}
ElseIf ($i -eq 470)
{
$strComputer | Out-file -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairfails.txt" -NoClobber -Append
Write-Host "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
Invoke-Command -Computer $strComputer { Get-Process -Name ccmrepair* | Stop-Process -Force }
Exit 1
}
}
########## END - PROCESS / PROGRESS CHECK AND RUN
}
}
Catch
{
Write-Host "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
Write-Host "[WARNING] This is most likely caused, because there is already a repair trigger running."
Write-Host "[WARNING] Wait a couple of minutes and try again."
# If the script keeps throwing errors, the WMI Namespace on $strComputer might be corrupt.
}
}
}
}
# RUN SCRIPT
Repair_SCCM
#Finished
I made sure i had the correct variable, by outputting it to the screen across various stages of the script, and it also creates the output, if i just create the variable and then directly output it, i.e
is reading from local profile folder not script folder. $arrComputer ends up reading nothing. This is because $Script_Parent is scoped within the Repair_SCCM function. To use the main script $Script_Parent, change your line 26 to look like:
A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the
Using variable is valid only if the script block is invoked on a remote computer.
At C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\Automated_SCCM_Repairv2.ps1:23 char:3
+ $arrComputer = Get-Content ($Using:SCRIPT_PARENT + "\input.txt")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : UsingWithoutInvokeCommand
The script itself works ok - It’s the lines i added
my bad, the diagnosis is correct - that’s the issue of variable scoping - $Script_Parent inside the function is not the same as $Script_Parent in the main script
the remedy was wrong. To pass the main script $Script_Parent variable to the function, you need to parameterize the function as in:
###########################################################
# AUTHOR : Marius / Hican - http://www.hican.nl - @hicannl
# DATE : 07-05-2012
# COMMENT : Automated remote repair of SCCM client on a
# list of machines, based on an input file.
###########################################################
#ERROR REPORTING ALL
# Set-StrictMode -Version latest
# Set-ExecutionPolicy bypass
#----------------------------------------------------------
#STATIC VARIABLES
#----------------------------------------------------------
$SCRIPT_PARENT = Split-Path -Parent $MyInvocation.MyCommand.Definition
# $SCRIPT_PARENT # Folder where script is - not current folder
#----------------------------------------------------------
#FUNCTION RepairSCCM
#----------------------------------------------------------
Function Repair_SCCM
{
[CmdletBinding()]
Param([Parameter(Mandatory=$true)][String]$SCRIPT_PARENT)
Write-Host "[INFO] Get the list of computers from the input file and store it in an array."
$arrComputer = Get-Content ($SCRIPT_PARENT + "\input.txt")
Foreach ($strComputer In $arrComputer)
{
#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
If ($strComputer.substring(0,1) -ne "*")
{
Write-Host "[INFO] Starting trigger script for $strComputer."
Try
{
$getProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
If ($getProcess)
{
Write-Host "[WARNING] SCCM Repair is already running. Script will end."
Exit 1
}
Else
{
Write-Host "[INFO] Connect to the WMI Namespace on $strComputer."
$SMSCli = [wmiclass] $SMSCli = [wmiclass] "\\$strComputer\root\ccm:sms_client"
Write-Host "[INFO] Trigger the SCCM Repair on $strComputer."
# The actual repair is put in a variable, to trap unwanted output.
$repair = $SMSCli.RepairClient()
Write-Host "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $strComputer."
########## START - PROCESS / PROGRESS CHECK AND RUN
# Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
Write-Host "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
For ($i = 0; $i -le 470; $i++)
{
$checkProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
Start-Sleep 1
Write-Progress -Activity "Repairing client $strComputer ..." -Status "Repair running for $i seconds ..."
If ($checkProcess -eq $Null)
{
Write-Host "[INFO] SCCM Client repair ran for $i seconds."
Write-Host "[INFO] SCCM Client repair process ran successfully on $strComputer."
Write-Host "[INFO] Check \\$strComputer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
$strComputer | Out-File -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairresults.txt" -NoClobber -Append
}
ElseIf ($i -eq 470)
{
$strComputer | Out-file -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairfails.txt" -NoClobber -Append
Write-Host "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
Invoke-Command -Computer $strComputer { Get-Process -Name ccmrepair* | Stop-Process -Force }
Exit 1
}
}
########## END - PROCESS / PROGRESS CHECK AND RUN
}
}
Catch
{
Write-Host "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
Write-Host "[WARNING] This is most likely caused, because there is already a repair trigger running."
Write-Host "[WARNING] Wait a couple of minutes and try again."
# If the script keeps throwing errors, the WMI Namespace on $strComputer might be corrupt.
}
}
}
}
# RUN SCRIPT
Repair_SCCM -SCRIPT_PARENT $SCRIPT_PARENT
#Finished
So, the author of this script came from vbScript with the Hungarian Notation (strComputer, arrComputer) and is also written like a vbScript, not a Powershell-y script. In Powershell, while you can write to files, you typically want objects because you can work with those objects to export them to a csv or even pipe them right to another function or cmdlet to perform another fix.
Write-Host should almost never be used. Write-Verbose gives you the flexibility to see the verbose messages or to simply remove the switch to run silently.
Start with something that is more Powershell driven, like this:
function Invoke-CMClientRepair {
[CmdLetBinding()]
param (
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$TimeOut = 470
)
begin {
}
process {
$results = foreach ($computer In $ComputerName) {
#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
if ($computer.substring(0,1) -ne "*") {
Write-Verbose "[INFO] Starting trigger script for $computer."
try {
$getProcess = Get-Process -Name ccmrepair* -ComputerName $computer
if ($getProcess) {
Write-Verbose "[WARNING] SCCM Repair is already running. Script will end."
Exit 1
}
else {
Write-Verbose "[INFO] Connect to the WMI Namespace on $computer."
$SMSCli = [wmiclass] $SMSCli = [wmiclass]"\\$computer\root\ccm:sms_client"
Write-Verbose "[INFO] Trigger the SCCM Repair on $computer."
# The actual repair is put in a variable, to trap unwanted output.
$repair = $SMSCli.RepairClient()
Write-Verbose "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $computer."
########## START - PROCESS / PROGRESS CHECK AND RUN
# Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
Write-Verbose "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
for ($i = 0; $i -le $TimeOut; $i++) {
$checkProcess = Get-Process -Name ccmrepair* -ComputerName $computer
Start-Sleep -Seconds 1
Write-Progress -Activity "Repairing client $computer ..." -Status "Repair running for $i seconds ..."
if ($checkProcess -eq $Null) {
Write-Verbose "[INFO] SCCM Client repair ran for $i seconds."
Write-Verbose "[INFO] SCCM Client repair process ran successfully on $computer."
Write-Verbose "[INFO] Check \\$computer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = "Success"
RepairTime = $i
}
}
ElseIf ($i -eq $TimeOut) {
Write-Verbose "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
Invoke-Command -Computer $computer { Get-Process -Name ccmrepair* | Stop-Process -Force }
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = "Failed - Timeout Reached"
RepairTime = $i
}
}
}
########## END - PROCESS / PROGRESS CHECK AND RUN
}
}
Catch
{
Write-Verbose "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
Write-Verbose "[WARNING] This is most likely caused, because there is already a repair trigger running."
Write-Verbose "[WARNING] Wait a couple of minutes and try again."
# if the script keeps throwing errors, the WMI Namespace on $computer might be corrupt.
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = ("Failed - {0}" -f $_)
RepairTime = $null
}
}
}
}
}
end {
$results
}
}
$computers = Get-Content C:\Scripts\Computers.txt
Invoke-CMClientRepair -Verbose #-ComputerName $computers
Output:
Invoke-CMClientRepair -Verbose
VERBOSE: [INFO] Starting trigger script for MY-PC.
VERBOSE: [INFO] Connect to the WMI Namespace on MY-PC.
VERBOSE: [WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error.
VERBOSE: [WARNING] This is most likely caused, because there is already a repair trigger running.
VERBOSE: [WARNING] Wait a couple of minutes and try again.
Status RepairTime Name
------ ---------- ----
Failed - Cannot convert value "\\MY-PC\root\ccm:sms_client" to type "System.Management.ManagementClass". Error: "Invalid namespace " MY-PC
PS C:\WINDOWS\system32> Invoke-CMClientRepair
Status RepairTime Name
------ ---------- ----
Failed - Cannot convert value "\\MY-PC\root\ccm:sms_client" to type "System.Management.ManagementClass". Error: "Invalid namespace " MY-PC
Once you get an object, you could do something like this to export the results:
Wow thank you, i really appreciate the effort you went into there to convert that into a powershell-y script!
A couple of odd things, once i run the script against a single PC, no matter what i change in the input.txt, it holds the previous computer name? even though it’s not hard coded anywhere, or even in the input.txt. Would this also run through a list of computers?
As i will most likely have 20-30 to do at a time! Also, it tends to record the failures, but not the successes? Many thanks guys
function Invoke-CMClientRepair {
[CmdLetBinding()]
param (
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$TimeOut = 470
)
begin {
}
process {
$results = foreach ($computer In $ComputerName) {
#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
if ($computer.substring(0,1) -ne "*") {
Write-Verbose "[INFO] Starting trigger script for $computer."
try {
$getProcess = Get-Process -Name ccmrepair* -ComputerName $computer
if ($getProcess) {
Write-Verbose "[WARNING] SCCM Repair is already running. Script will end."
Exit 1
}
else {
Write-Verbose "[INFO] Connect to the WMI Namespace on $computer."
$SMSCli = [wmiclass] $SMSCli = [wmiclass]"\\$computer\root\ccm:sms_client"
Write-Verbose "[INFO] Trigger the SCCM Repair on $computer."
# The actual repair is put in a variable, to trap unwanted output.
$repair = $SMSCli.RepairClient()
Write-Verbose "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $computer."
########## START - PROCESS / PROGRESS CHECK AND RUN
# Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
Write-Verbose "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
for ($i = 0; $i -le $TimeOut; $i++) {
$checkProcess = Get-Process -Name ccmrepair* -ComputerName $computer
Start-Sleep -Seconds 1
Write-Progress -Activity "Repairing client $computer ..." -Status "Repair running for $i seconds ..."
if ($checkProcess -eq $Null) {
Write-Verbose "[INFO] SCCM Client repair ran for $i seconds."
Write-Verbose "[INFO] SCCM Client repair process ran successfully on $computer."
Write-Verbose "[INFO] Check \\$computer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = "Success"
RepairTime = $i
}
}
ElseIf ($i -eq $TimeOut) {
Write-Verbose "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
Invoke-Command -Computer $computer { Get-Process -Name ccmrepair* | Stop-Process -Force }
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = "Failed - Timeout Reached"
RepairTime = $i
}
}
}
########## END - PROCESS / PROGRESS CHECK AND RUN
}
}
Catch
{
Write-Verbose "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
Write-Verbose "[WARNING] This is most likely caused, because there is already a repair trigger running."
Write-Verbose "[WARNING] Wait a couple of minutes and try again."
# if the script keeps throwing errors, the WMI Namespace on $computer might be corrupt.
New-Object -TypeName PSObject -Property @{
Name = $Computer
Status = ("Failed - {0}" -f $_)
RepairTime = $null
}
}
}
}
}
end {
$results
}
}
$computers = Get-Content C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\input.txt
$results = Invoke-CMClientRepair -Verbose #-ComputerName $computers
$results | Export-Csv -Path C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\Results.csv -NoTypeInformation
output
VERBOSE: [INFO] Starting trigger script for 5CG8201RWF.
VERBOSE: [INFO] Connect to the WMI Namespace on 5CG8201RWF.
VERBOSE: [INFO] Trigger the SCCM Repair on 5CG8201RWF.
VERBOSE: [WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error.
VERBOSE: [WARNING] This is most likely caused, because there is already a repair trigger running.
VERBOSE: [WARNING] Wait a couple of minutes and try again.