1

Overview: I have a script I am using to query new application requests within SCCM via WMI. The script works perfectly when launched manually within a PowerShell console (either elevated or not, doesn't matter). I need to run the script through Task Scheduler. Currently set up to run with admin credentials and the checkbox for 'Run with highest privileges' is checked.

The Issue: Won't run correctly from Task Scheduler within Windows Server 2008 R2. No errors are reported (task scheduler returns error code of 0) but it doesn't seem to proceed past the line that reads:

$GetAppRequest = Get-WmiObject -Class SMS_UserApplicationRequest -Namespace root/SMS/site_$SiteCode | Where-Object {$_.CurrentState -like "1"} | ForEach-Object {

Here is the full script:

#Hard-coded variables
$SiteCode = "MySiteCode"
$ComputerName = "My-SCCM-Server"
$GUIDFilePath = "C:\Scripts\SCCM\GUIDList.txt"
$FilePath = "C:\Scripts\SCCM"
$smtpServers = "smtp1.domainname.com","smtp2.domainname.com"
$reliableSmtpServer = $null
$logpath = "C:\Scripts\SCCM\RequestLog.txt"

#logging functionality
function log($message, $type){
  $date = get-date
  $string = "$date $type : $message" | Out-File $logpath -Append
}

#does log exist?
if (gi -Path $logpath -ErrorAction SilentlyContinue){
  #yep, the log exists!
  $logContent = cat $logpath
  log -message "Script called and log opened for writing." -type "Info"
} else {
  #nope, the log doesn't exist, let's make one.
  write "Can't find log file, creating a new one."
  $newFileResult = New-Item -Path $logpath -ItemType File -ErrorAction Stop
  if ($newFileResult.Exists){
    log -message "new log file created" -type "Info"
  } #end if
} #end else

#Email variables
$from = "no.reply@domainname.com"
$to = "sccm-admin@domainname.com"
$subject = "New SCCM Application Approval Requests"

#Determine which SMTP to use.
$smtpServers | ForEach-Object {
  if ($reliableSmtpServer -eq $null){
    if (Test-Connection -ComputerName $_ -ErrorAction SilentlyContinue){
      write "Reliable SMTP server found: $_"
      $reliableSmtpServer = $_       
    } #end if test-connection
  } #end if reliableSmtpServer exists
} #end foreach SMTP server

if ($reliableSmtpServer){
  log -message "Reliable SMTP server found, $reliableSmtpServer" -type "Info"
} else {
  log -message "No reliable SMTP server could be found" -type "Error"
}

#Get the entries from GUIDList.txt
if ($GetGUID = Get-Content -Path $GUIDFilePath -ErrorVariable guidReadError {
  write "Successfully read $GUIDFilePath"
  log -message "Successfully read $GUIDFilePath" -type "Info"
} else {
  Write-Error -Message "Couldn't read GUIDfile..."
  log -message "Failed to read GUIDFile" -type "Error"
}

#Get all Application Requests with a CurrentState of "1"
log -message 'Attempting to get all Application Requests with a CurrentState of 1' -type "Info"
$GetAppRequest = Get-WmiObject -Class SMS_UserApplicationRequest -Namespace root/SMS/site_$SiteCode | Where-Object {$_.CurrentState -like "1"} | ForEach-Object {
  log -message "App found, $_.Application" -type "Info"
  if ($GetGUID -contains $_.RequestGuid) {
    Write-Host "Application request $($_.RequestGuid) already present"
    log -message "Application request $($_.RequestGuid) already present" -type "Info"
  } else {
    $appUser = $_.User
    $appName = $_.Application
    $appComment = $_.Comments
    $Body = @"
    Application request: $appName
    User: $appUser

    Comment: $appComment
    "@ #This row can't contain any blank spaces or tabs

    log -message "New record found: $appUser, $appName, $appComment" -type "Info"

   #Email configuration
    Send-MailMessage -SmtpServer $reliableSmtpServer -From $from -To $to -Subject $subject -Body $body -ErrorVariable mailError
   if (!($mailError)){
     write "Message successfully sent to : $to"
     log -message "Message successfully sent to : $to" -type "Info"
   } else {
     Write-Error -message "Failed to send email!"
     log -message "Failed to send email!" -type "Error"
   } #end else

   #Append the current objects GUID to GUIDList.txt
   Write "Appending $($_.RequestGUID) to $GUIDFilePath"
   log -message "Appending $($_.RequestGUID) to $GUIDFilePath" -type "Info"
$_.RequestGuid | Out-File $GUIDFilePath -Append

  } #end else statement
} #end forEach

#Remove the GUIDList.txt file and re-create it when there's more than 100 entries
$GUIDCount = $GetGUID.Count
if ($GUIDCount -gt 100) {
  log -message "Greater than 100 GUID entries, clearing list." -type "Info"
  Get-Item $GUIDFilePath | Remove-Item
  New-Item -Path $FilePath -Name GUIDList.txt -ItemType file
}

#Create a new log once the log file exceeds 1000 lines.
$logCount = $logContent.Count
if ($logCount -gt 1000) {
  log -message "Log file is too long, removing log" -type "Warning"
  Remove-Item $logpath
}

Here is the scheduled task XML:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2014-05-09T17:10:48.6636926</Date>
    <Author>domainname\myUserAccount</Author>
    <Description>Runs a script located at C:\scripts\SCCM to determine if there are any new application requests and notify IT staff via Email.</Description>
  </RegistrationInfo>
  <Triggers>
    <TimeTrigger>
      <Repetition>
        <Interval>PT15M</Interval>
        <StopAtDurationEnd>false</StopAtDurationEnd>
      </Repetition>
      <StartBoundary>2014-05-09T17:12:04</StartBoundary>
      <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
      <Enabled>true</Enabled>
    </TimeTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>domain\AdminAccount</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>powershell.exe</Command>
      <Arguments>-noprofile -file "C:\scripts\sccm\Notify.ps1"</Arguments>
    </Exec>
  </Actions>
</Task>
Jake Nelson
  • 1,748
  • 13
  • 22
  • And none of the solutions offered in the other *my PS script won't run as a scheduled task* questions helped? I count 7 potential duplicates in the Related list to the right, without even searching for others. Have you investigated the solutions to those other questions? – Ken White Apr 27 '16 at 01:51
  • Yes, I have read those. None were helpful as they tended to relate either to things that could not be called without an interactive session or elevation. I've already discounted elevation as I'm running with highest privs and admin credentails. As for interaction sessions, it's just a WMI query so it should work? If you spot something specific that you think would be helpful, please point it in my direction. – Jake Nelson Apr 27 '16 at 01:55
  • I would suggest you to add a `trap` to capture any exceptions. Can you add `trap { log -message ($_ | fl * -f | out-string) -type "Exception" }` after your log function and check the log whether any exception was thrown? – Martin Brandl Apr 29 '16 at 07:25
  • I assume that you have tried (1) running the script interactively as AdminAccount and (2) running from the task scheduler with the account that you tested successfully with? – Charlie Joynt Apr 29 '16 at 23:29
  • 1
    I notice that you use `-noprofile` in the arguments to `powershell.exe`. Could you be relying on a profile to load modules / WMI classes / etc? Adding some error handling as per @jisaak suggested would be good. – Charlie Joynt Apr 29 '16 at 23:39

2 Answers2

2

Did you mean to assign the results of the Get-WmiObject ... | Foreach-Object { line to the $GetAppRequest variable? What I mean is that the output of the Foreach-Object loop are being caught by that assignment which doesn't look intentional since you don't use that variable again.

I'd suggest that you perform the assignment to the variable then pass the variable to the Foreach-Object cmdlet separately with some logging in between. Also, we could wrap the Get-WmiObject in a try{}catch{} construction to capture any errors the cmdlet might throw; to ensure the error is caught, we set -ErrorAction Stop:

try {
  $GetAppRequest = Get-WmiObject -Class SMS_UserApplicationRequest -Namespace root/SMS/site_$SiteCode -ErrorAction Stop
}
catch {
  log -message "Get-WmiObject cmdlet failed" -type "Error"
  log -message $_.Exception.Message.ToString() -type "Error"
}

if(-not $GetAppRequest) {
  log -message "Failed to retrieve WMI data" -type "Error"

} elseif(-not ($GetAppRequest = $GetAppRequest | Where-Object {$_.CurrentState -like "1"})) {
  log -message "No results with CurrentState = 1" -type "Info"
}

$GetAppRequest | ForEach-Object {
  ...
Charlie Joynt
  • 4,411
  • 1
  • 24
  • 46
  • Aren't you missing a `-not` in the elseif-test? – Frode F. Apr 30 '16 at 19:15
  • Quite possible. Was writing this one "blind" instead of testing everything I submit! Will edit... – Charlie Joynt May 01 '16 at 09:05
  • I followed your suggestions @CharlieJoynt. The log file now results in "Failed to retrieve WMI data" when run from task manager. Still not sure why this is occuring though. I thought what someone else said about something unique in the profile could be true but I'm not using any $PROFILE documents on that server. Also, I get the same error regardless of having the -noprofile switch in the action of the scheduled task, I just chose to include it since I didn't see a point in loading the user profile. – Jake Nelson May 04 '16 at 00:10
  • At least we now know that it is the `Get-WmiQuery` that is failing (or simply returning nothing). Maybe the next thing is to try to wrap the cmdlet in a `try{}catch{}` test. Will update my answer! – Charlie Joynt May 04 '16 at 08:05
  • @CharlieJoynt, Your answer allowed me to resolve the problem. The issue seemed to be with permissions. The user account I was using for the scheduled task failed as it couldn't read the SCCM WMI properties, it is an admin on the system, has the run as service priv and can read non sccm WMI entries so it's def a weird one but I changed the scheduled task to run under SYSTEM and it now works. Thank you for your help. – Jake Nelson May 04 '16 at 17:34
  • Good stuff. :-) Sounds like you have another question about remote WMI permissions too! :-D – Charlie Joynt May 04 '16 at 20:04
-1

Check with "run only when the user is logged on" and hidden checkbox unchecked.

Farhad Farahi
  • 35,528
  • 7
  • 73
  • 70
  • Can you please explain why you think that would be required? I'm running the script on a server under different credentials than my user account. – Jake Nelson Apr 27 '16 at 12:48
  • Well, I've had the same problem with rare scripts, still running as logged on user on servers. even saw some .net service that uses adobe reader commandline needs a logged on user to work. So try it out and give us feedback please. – Farhad Farahi May 01 '16 at 04:14