Showing posts with label Windows Server 2008. Show all posts
Showing posts with label Windows Server 2008. Show all posts

Tuesday, March 22, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 3: Daily Tasks

In my previous blog posts, "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task" and "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 2: Weekly Tasks", I explored the various settings required for scheduled tasks for those given date ranges. This post deals with the daily reoccurring tasks that administrators need to schedule, sometimes more than once a day. In the code sample below, I schedule a fictitious log cleanup PowerShell script to run three times a day; midnight, 8 am and 4 pm. For security reasons, after running this script, you should close the PowerShell command window as the password submitted for the Task Security Principal will remain in the command buffer allowing anyone with access to your keyboard to "up arrow" and reveal it.
# Parameters to modify
$taskName = "Start-TriDailyLogCleanup"
$taskWorkingDirectory = "C:\PowerShell"
$taskPath = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskArguments = "-command `"$taskWorkingDirectory\$taskName.ps1`""
$taskAuthor = "ad\myaccount"
$taskDescription = "The Tri-Daily Log Cleanup on the servers."
$taskSecurityPrincipal = "ad\maintenance"
$taskShedulerTaskFolder = "\MyTasks"
$startTimes = @((Get-Date "03/23/2011 00:00:00" -Format s),(Get-Date "03/23/2011 08:00:00" -Format s),(Get-Date "03/23/2011 16:00:00" -Format s))

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskShedulerTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

foreach($startTime in $startTimes) {
	$executionTrigger = $taskTriggers.Create(2) 
	$executionTrigger.StartBoundary = $startTime
}

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = $taskPath
$taskAction.Arguments = $taskArguments
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security. Close the PowerShell command window to increase security.
Clear-Variable -name password

Tuesday, February 22, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 2: Weekly Tasks

In my previous blog post, "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task", I laid the groundwork for the various ways you can schedule tasks in Windows Server 2008. This installment of the series shows how to create a weekly task with multiple triggers. In the code sample below, I schedule a report to run on Monday (2), Wednesday (8) and Thursday (16) at 4:30 pm local time to the server. To extend this example further, you could use a hash table to establish a different time of day for each of the day of the week task triggers.
# Parameters to modify
$taskName = "Export-TriWeeklyDataDeobfuscationReport.ps1"
$taskWorkingDirectory = "C:\PowerShell"
$taskAuthor = "ad\myaccount"
$taskDescription = "The Tri-Weekly Data Deobfuscation Report"
$taskSecurityPrincipal = "ad\myaccount"
$taskShedulerTaskFolder = "\MyTasks"
$startTime = (Get-Date "02/28/2011 16:30:00" -Format s)
$daysOfWeek = @("2","8","16") # http://msdn.microsoft.com/en-us/library/aa381905(v=VS.85).aspx

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskShedulerTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

foreach($dayOfWeek in $daysOfWeek) {
 $executionTrigger = $taskTriggers.Create(3) 
 $executionTrigger.DaysOfWeek = $dayOfWeek
 $executionTrigger.StartBoundary = $startTime
}

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskAction.Arguments = "-command `"$taskWorkingDirectory\$taskName`""
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security. Close the PowerShell command window.
Clear-Variable -name password

Friday, February 11, 2011

Audit Tivoli Storage Manager Backup Client Schedules on Windows Servers

As any System Administrator knows you need to have a good backup. IBM Tivoli Storage Manager is a common platform used in large environments. It's a client-server platform with an installed client that once registered with the TSM server, backups the local server (client) to the destination backup storage. Keeping track of client versions, client nodes and log files can be burdensome if you have multiple TSM servers in different data centers. To keep abreast of this information, auditing is key. Knowing how the configuration of Tivoli Storage Manager Backup Client is stored for schedules gives us the ability to audit them. Most of the information resides in the registry under "HKEY_LOCAL_MACHINE\SOFTWARE\IBM". In the code example below, a list of server fully qualified domain names are stored in a text file and read into the script. To obtain this information using PowerShell, I am using the Microsoft.Win32.RegistryKey class to navigate the branches of the registry containing the configuration values of interest to me. One of those values, the option file path, allows me to obtain even more information about the backup -- the TSM server and port used for client-server communication. I take the local path of the option file and translate it to a UNC Path that I can remotely access, load the file into a foreach loop, attempt to located those two values and store them into strings. Just in case this process fails, I pre-populate the two strings with "FAILED". There are a few reasons that this could fail, the security context that is trying to access the option file doesn't have rights, the file doesn't exist (an uninstall failed to clear the registry) or the variables in the opt file we are searching for are not present.

This script does not report the servers lacking a client schedule. It is really important to know this information. Without a backup, you do not have recovery! I have another method that ensures that all servers that require backup are monitored for that process and if it was successful. If you do not, I would take some time to alter the script record the servers without schedules.

If you are running this script in an environment with both 32 bit and 64 bit Windows Server versions, you need to execute it from a 64 bit version of Windows Server. If you run it from a 32 bit and query the registry of a 64 bit server, the returned registry will be the 32 bit compatibility hive. TSM Backup Client has a native 64 bit version.
param([string]$file)

Function Get-TSMInfo($server, $tsmInfo) {
 $key = "SOFTWARE"
 $hKey = [Microsoft.Win32.RegistryHive]::LocalMachine
 $baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hKey, $server)
 foreach($rootKeyValue in ($baseKey.OpenSubKey($key)).GetSubKeyNames()) {
  if($rootKeyValue -eq "IBM" -and ($baseKey.OpenSubKey("$key\IBM\ADSM\CurrentVersion")).SubKeyCount -gt 2) {
   $tsmVersion = ($baseKey.OpenSubKey("$key\IBM\ADSM\CurrentVersion\BackupClient")).GetValue("PtfLevel")
   $tsmPath = ($baseKey.OpenSubKey("$key\IBM\ADSM\CurrentVersion\BackupClient")).GetValue("Path")
   $key = "SYSTEM\CurrentControlSet\Services"
   if($tsmVersion -ne "" -and $tsmPath -ne "") {
    foreach($keyValue in ($baseKey.OpenSubKey($key)).GetSubKeyNames()) {
     foreach($subKeyValue in ($baseKey.OpenSubKey("$key\$keyValue")).GetSubKeyNames()) {
      $clientNodeName = ""
      $errorLog = ""
      $optionsFile = ""
      $scheduleLog = ""
      if(($baseKey.OpenSubKey("$key\$keyValue").GetValue("Start")) -eq "2") {
       if($subKeyValue -eq "Parameters") {
        foreach($value in ($baseKey.OpenSubKey("$key\$keyValue\Parameters")).GetValueNames()) {
         if($value -eq "clientNodeName") {
          $clientNodeName = ($baseKey.OpenSubKey("$key\$keyValue\Parameters")).GetValue($value)
         } elseif($value -eq "errorLog") {
          $errorLog = ($baseKey.OpenSubKey("$key\$keyValue\Parameters")).GetValue($value)
         } elseif($value -eq "optionsFile") {
          $optionsFile = ($baseKey.OpenSubKey("$key\$keyValue\Parameters")).GetValue($value)
         } elseif($value -eq "scheduleLog") {
          $scheduleLog = ($baseKey.OpenSubKey("$key\$keyValue\Parameters")).GetValue($value)
         }
        }
       }
      }
      if($clientNodeName -ne "" -and $errorLog -ne "" -and $optionsFile -ne "" -and $scheduleLog -ne "") {
       $optionsFileUncPath = ("\\$server\" + ($optionsFile.SubString(0,1) + "$" + $optionsFile.SubString(2)))
       $tsmServer = "FAILED"
       $tsmClientPort = "FAILED"
       if(Test-Path -path $optionsFileUncPath) {
        foreach($line in (Get-Content -path $optionsFileUncPath)){
         if($line -match "TCPSERVERADDRESS") {
          $tsmServer = ($line -replace "TCPSERVERADDRESS","").Trim()
         }
         if($line -match "TCPCLIENTPORT") {
          $tsmClientPort = ($line -replace "TCPCLIENTPORT","").Trim()
         }
        }
       }
       $clientNodeInformation = New-Object -typeName PSObject
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "server" -value $server
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "tsmVersion" -value $tsmVersion
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "installPath" -value $tsmPath
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "tsmServer" -value $tsmServer
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "tsmClientPort" -value $tsmClientPort
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "scheduleName" -value $keyValue
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "clientNodeName" -value $clientNodeName
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "optionsFile" -value $optionsFile
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "scheduleLog" -value $scheduleLog
       Add-Member -inputObject $clientNodeInformation -type NoteProperty -name "errorLog" -value $errorLog
       $tsmInfo += $clientNodeInformation
      }
     }
    }
   }
  }
 }
 return $tsmInfo
}

#--------------------------------------------------------------------------------------------------#

if(![bool]$file) {
 Write-Host "You are missing the `"-file`" command line argument" -foregroundColor Red
 Write-Host ""
 Write-Host "This is the file that contains the list of server fully qualified"
 Write-Host "domain names that you want to audit their TSM Clients"
 Write-Host ""
 while((![bool]$file)) {
  $file = Read-Host -prompt "`tFile"
 }
 Write-Host ""
}
if(!(Test-Path -path $file)) {
 Write-Host "Unable to locate: $file" -foregroundColor Red
 Write-Host ""
 Write-Host "Please review the correct path"
 Write-Host ""
 while(!(Test-Path -path $file)) {
  $file = Read-Host -prompt "`tFile"
 }
 Write-Host ""
}
 
$answer = $null
while($answer -ne "Y" -and $answer -ne "N") {
 $answer = Read-Host -prompt "Are you sure you want to use $file (Y/N)?"
}
 
if($answer -eq "N") {
 Write-Host ""
 Write-Host "Quiting..." -foregroundColor Yellow
 exit
}

$tsmInfo = @()

foreach($server in (Get-Content -path $file)) {
 Write-Host "Auditing $server"
 $tsmInfo = @(Get-TSMInfo $server $tsmInfo)
 Write-Host ("Schedules found: " + $tsmInfo.count)
}

$tsmInfo | Export-Csv -path "TSM Client Audit.csv" -noTypeInformation

Tuesday, February 1, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task

Windows Server 2008 updates one of my biggest complaints in Windows Server 2003 relating to scheduling automated tasks programatically. I have always appreciated the ease of doing this in Linux/UNIX via crontab. That ease was just not there in Windows Server 2003 as at is very clunky. However, I feel that creating a scheduled task in Windows Server 2003 manually is much easier and faster than Windows Server 2008 -- but that's me. In the example below, I create a monthly scheduled task that occurs at 6 am on the 15th of the month starting in February 2011. I am storing the task in a subfolder off the root in Scheduled Tasks that I manually created to segregate the task from other application registrations. The task will run under another security context than the one that created the task. I would prefer to capture the password securely for that context and supply it that way to the RegisterTaskDefinition but I have not discovered how to perform this with the provider. To provide a modicum of security, I clear the $password variable at the end of the script. To reduce security risks in your environment, always execute scripts with the least security privilege required to perform the function.

In future posts, I will provide further examples that show the different intervals that you can schedule tasks. This example is a basic task as there are many areas where you can fine tune the task.
# Parameters to modify
$taskName = "Export-InterestingInformation.ps1"
$taskWorkingDirectory = "C:\PowerShell"
$taskAuthor = "ad\myaccount"
$taskDescription = "The Monthly Interesting Information Report"
$taskSecurityPrincipal = "ad\reporting"
$taskSheduledTaskFolder = "\MyTasks"
$startTime = (Get-Date "02/15/2011 06:00:00" -Format s)

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskSheduledTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

$executionTrigger = $taskTriggers.Create(4) 
$executionTrigger.DaysOfMonth = 16384 # http://msdn.microsoft.com/en-us/library/aa380735(v=vs.85).aspx
$executionTrigger.StartBoundary = $startTime

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskAction.Arguments = "-command `"$taskWorkingDirectory\$taskName`""
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security
Clear-Variable -name password

Tuesday, January 18, 2011

Resolving System State Backup failures in TSM

An issue with IBM Tivoli backups I encounter on regular basis are failures with the System State portion of the TSM backup process (ANS5258E error in the dsmsched.log). IBM would have you reboot the server to resolve the issue. This solution works but we are better than that. The issue lies with Volume Shadow Service writers. To determine the culprit, I need to know which of the writers is having the issue. I use the command line program "vssadmin" to discover the problematic writer.

Sample Output:

C:\>vssadmin list writers
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001 Microsoft Corp.

Writer name: 'System Writer'
   Writer Id: {e8132975-6f93-4464-a53e-1050253ae220}
   Writer Instance Id: {ea8c901f-77c6-4a3e-a673-7d2276d7a4bf}
   State: [7] Failed
   Last error: No error

Writer name: 'Event Log Writer'
   Writer Id: {eee8c692-67ed-4250-8d86-390603070d00}
   Writer Instance Id: {d787de6c-2b5d-45b9-8916-4351737ec993}
   State: [1] Stable
   Last error: No error

Writer name: 'Registry Writer'
   Writer Id: {afbab4a2-367d-4d15-a586-71dbb18f8485}
   Writer Instance Id: {76401cb9-4cf5-4c0f-bf8f-2adb4672f119}
   State: [1] Stable
   Last error: No error

Writer name: 'COM+ REGDB Writer'
   Writer Id: {542da469-d3e1-473c-9f4f-7847f01fc64f}
   Writer Instance Id: {59267df6-52ac-4679-ac8e-536527bb195f}
   State: [1] Stable
   Last error: No error

Writer name: 'WMI Writer'
   Writer Id: {a6ad56c2-b509-4e6c-bb19-49d8f43532f0}
   Writer Instance Id: {21441d8f-4ce4-4c53-b817-f7f899a154c0}
   State: [1] Stable
   Last error: No error

Writer name: 'IIS Metabase Writer'
   Writer Id: {59b1f0cf-90ef-465f-9609-6ca8b2938366}
   Writer Instance Id: {a5e5576d-9f8d-4933-9f51-c0e0895eafaf}
   State: [1] Stable
   Last error: No error

Writer name: 'BITS Writer'
   Writer Id: {4969d978-be47-48b0-b100-f328f07ac1e0}
   Writer Instance Id: {078d566b-625c-4daa-9535-9546a57ea482}
   State: [1] Stable
   Last error: No error

If any of these writers report anything but their state as 'Stable' and last error as 'No error',  you need to restart the associated service for that writer. In the above example, we would restart Cryptographic Services to resolve the System Writer issue. Here is a list of writers and associated services I have collected and is by no means a comprehensive list but what I have encountered the most when resolving this issue.
  • System Writer -- restart Cryptographic Services
  • Event Log Writer -- restart Event Log (never seen this before)
  • Registry Writer -- restart Volume Shadow Copy
  • COM+ REGDB Writer -- restart Volume Shadow Copy
  • WMI Writer -- restart Windows Management Instrumentation
  • IIS Metabase Writer -- restart IIS Admin Service (not always 100% effective)
  • BITS Writer -- restart Background Intelligent Transfer Service
  • Shadow Copy Optimization Writer -- restart Volume Shadow Copy
The simple way to check if you have resolved your issue is to rerun 'vssadmin list writers' and ensure that no writers have issues then run 'dsmc ba -optfile=dsm.opt' from the baclient directory and watch the manual backup for system state success. 

Monday, January 3, 2011

PowerShell, Windows Server 2008 & Excel 2010 64 Bit Scheduled Task Error

Are you trying to use PowerShell to generate a spreadsheet via a Scheduled Task using a 64 bit version of Excel 2010 on Windows Server 2008 64 bit and get the following error?

'Exception calling "SaveAs" with "1" argument(s): "SaveAs method of Workbook class failed"'

Yet, you do not get this error when running the script from the PowerShell command line? Frustrating isn't it? Quit banging your head against your desk and make sure this path is valid via PowerShell.
Test-Path -path $env:windir\System32\config\systemprofile\Desktop
If the return from this command is "False", run the following to create the directory.
New-Item -path $env:windir\System32\config\systemprofile\Desktop -type Directory