Quantcast
Channel: Scripting – Robin CM's IT Blog
Viewing all 27 articles
Browse latest View live

Missing row when using ExecuteReader to fill a DataTable

$
0
0

I’ve been using the following PowerShell code to retrieve the contents of an SQL Server table and store it in a DataTable for fast local processing:

$BuildDBConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList "Server=RCMSQL01;Database=TSBuild;Integrated Security=SSPI"
$BuildDBConnection.Open()
# Get Main table
$SQLCommand = $BuildDBConnection.CreateCommand()
$SQLCommand.CommandText = ("SELECT * FROM Main")
$SQLReader = $SQLCommand.ExecuteReader()
if($SQLReader.Read()){
    $MainTable = New-Object System.Data.DataTable
    $MainTable.Load($SQLReader)
    $SQLReader.Close()
}
$MainTable | ft -AutoSize

It looked like it had been working fine. However, today I noticed that some data was missing. Specifically the first row of data as seen if you view the SQL table via a Select query (e.g. via SQL Server Management Studio).

I eventually tracked this down to being caused by the if statement, and thus probably the the $SQLReader.Read() is retrieving the first row of the table, and then the $DataTable.Load() is getting everything else.

I have two fixes so far:

1) Don’t use the if statement, just try and load the $SQLReader object straight into the DataTable.

2) Use a SQLDataAdapter instead:

$BuildDBConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList "Server=RCMSQL01;Database=TSBuild;Integrated Security=SSPI"
$BuildDBConnection.Open()
# Get Main table
$SQLCommand = $BuildDBConnection.CreateCommand()
$SQLCommand.CommandText = ("SELECT * FROM Main")
$SQLAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $SQLCommand
$MainTable = New-Object System.Data.DataTable
$SQLAdapter.Fill($MainTable)
$MainTable | ft -AutoSize


PowerShell: Finding user sessions on RDSH servers

$
0
0

If you’ve been working with Citrix Metaframe/Presentation Server/XenApp and/or Microsoft Terminal Server/Terminal Services/Remote Desktop Session Host for a while you’ll probably be familiar with the command line utility quser (or query user). It returns a tabular output showing details of users currently logged on:

 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 a-user                ica-tcp#15          1  Active          7  08/04/2014 07:19
 an-other              ica-tcp#16          2  Active          .  08/04/2014 08:26
 all-rest              ica-tcp#19          3  Active          .  08/04/2014 08:36
 no-play               ica-tcp#20          5  Active          .  08/04/2014 08:37
 b-usy                 ica-tcp#18          4  Active          .  08/04/2014 08:30

You can get the same information from a remote server by appending /server:<servername> onto the end of the command.

If no users are logged on to the server being queried then the command returns (via StdErr):

No User exists for *

This is a very useful command.Very untouched by Microsoft for approaching the last 15 years…

Which is a shame because the output format leaves a little to be desired. For example if there are some disconnected sessions the table format goes a bit awry:

 USERNAME           SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 a-user                                 2  Disc        none   07/04/2014 09:58
 an-other           ica-tcp#69          1  Active          .  08/04/2014 08:15

Looks ok on screen but try and parse that empty SESSIONAME column from a scripting point of view, nasty.

So you can use this command to retrieve information, but for anything other than a basic test of “is somebody logged on or not” for scripting purposes it’s not ideal.

Oh, and there’s basically no (built in) PowerShell equivalent. You might be able to use Get-RDUserSession, or not, depending on whether your server is part of a Remote Desktop Deployment.

So, I tried using WMI to get a count of user sessions:

$OSVersion = (Get-WmiObject -Class Win32_OperatingSystem -ComputerName $NewVMName).Version
if($OSVersion -eq "5.2.3790"){
    # Server 2003
    $ActiveSessions = (Get-WmiObject -Class Win32_PerfRawData_TermService_TerminalServices -ComputerName $NewVMName).ActiveSessions
    $InactiveSessions = ((Get-WmiObject -Class Win32_PerfRawData_TermService_TerminalServices -ComputerName $NewVMName).InactiveSessions - 1) # subtract one for "Console" session
} else {
    $ActiveSessions = (Get-WmiObject -Class Win32_PerfRawData_LocalSessionManager_TerminalServices -ComputerName $NewVMName).ActiveSessions
    $InactiveSessions = ((Get-WmiObject -Class Win32_PerfRawData_LocalSessionManager_TerminalServices -ComputerName $NewVMName).InactiveSessions - 2) # subtract two for "Service" and "Console" sessions
}

Which as you can see, is a little fiddly due to the classes being different between 2003 and newer OSs, and that the console and service sessions are included. It also doesn’t tell you if there is a user logged on to the (local) console, as there is always a console session present. Not ideal if you’re wanting to ensure there are no people logged on to a server prior to rebooting/rebuilding it.

So in the end I’ve gone back to old faithful, quser:

$Server = "SomeServerName"
$ErrorActionPreference = "Continue"
$QUResult = quser /server:$Server 2>&1
$ErrorActionPreference = "Stop"
if($QUResult -notmatch "no user exists for"){
    Write-Host "Sessions found" -ForegroundColor Red
    $QUResult
}

Note that I’m setting $ErrorActionPreference to “Continue” because I tend to set it to “Stop” at the top of my scripts (not shown in the chunk above) and because quser returns the “No User exists for *” text via StdErr, the script will stop with an error if there are no users logged on. That’s also the reason for the 2>&1 on the end of the quser command line – re send the StdErr output to the same place as StdOut, to ensure they both end up in the $QUResult variable irrespective of whether users are logged on or not.


PowerShell script to get temperature from NetBotz

$
0
0

Wrote this to extract the temperature reading from a few NetBotz devices that I have in my datacentres.

Note that the second time I call the function, it is getting the temperature from a second (non-docked) sensor pod attached to the first NetBotz.

function Get-NetBotzTemp($Name,$URL,$User,$Pass){
    # set up web client and authentication
    $WebClient = New-Object System.Net.WebClient
    $CredCache = New-Object System.Net.CredentialCache
    $Credential = New-Object System.Net.NetworkCredential($CredUser,$CredPass)
    $CredCache.Add($URL,"Basic", $Credential)
    $WebClient.Credentials = $CredCache
    # get text contents of web page
    $WebPage = $WebClient.DownloadString($URL)
    # ditch the first part of the page, up until the numeric temperature value
    $LastPart = ($WebPage -split ',*_TEMP.+pic">')[1]
    # ditch the last part of the page, after the numeric temperature value
    $Farenheight = ($LastPart -split ' °F')[0]
    # NetBotz defaults to farenheit based on what it think the browser is, so convert the centigrade
    $Celcius = ($Farenheight - 32) / 1.8
    $Celcius = "{0:N1}" -f $Celcius
    # display output
    Write-Host ("Temperature in "+$Name+" is $Celcius")
}

Get-NetBotzTemp -Name "DC1" -URL "http://192.168.100.21/pages/status.html" -User "netbotz" -Pass "netbotz"
Get-NetBotzTemp -Name "DC2" -URL "http://192.168.100.21/pages/status.html?encid=nbHawkEnc_1" -User "netbotz" -Pass "netbotz"
Get-NetBotzTemp -Name "DC3" -URL "http://192.168.100.22/pages/status.html" -User "netbotz" -Pass "netbotz"


PowerShell: Find MTU

$
0
0

I had some issues caused by MTU recently, and decided to write a script to test for the Maximum Transmission Unit that a network or host could cope with.

I originally started off by using the PowerShell Test-Connection cmdlet, but then realised that it doesn’t allow you to set the DF bit (do not fragment) which is required for MTU testing, so I switched it out for the ping command.

The script works by trying buffer sizes that are calculated as the halfway point between a minimum and maximum value. These two points converge as the preceding test either passes or fails, which makes for a fairly swift determination of the MTU.

#set BufferSizeMax to the largest MTU you want to try, usually 1500 or up to 9000 if using Jumbo Frames
$BufferSizeMax = 1500
#set TestAddress to the name or IP address you wish to test against
$TestAddress   = "www.bbc.co.uk"

$LastMinBuffer=$BufferSizeMin
$LastMaxBuffer=$BufferSizeMax
$MaxFound=$false

#calculate first MTU attempt, halfway between zero and BufferSizeMax
[int]$BufferSize = ($BufferSizeMax - 0) / 2
while($MaxFound -eq $false){
    try{
        $Response = ping $TestAddress -n 1 -f -l $BufferSize
        #if MTU is too big, ping will return: Packet needs to be fragmented but DF set.
        if($Response -like "*fragmented*"){throw}
        if($LastMinBuffer -eq $BufferSize){
            #test values have converged onto the highest working MTU, stop here and report value
            $MaxFound = $true
            Write-Host "found."
            break
        } else {
            #it worked at this size, make buffer bigger
            Write-Host "$BufferSize" -ForegroundColor Green -NoNewline
            $LastMinBuffer = $BufferSize
            $BufferSize = $BufferSize + (($LastMaxBuffer - $LastMinBuffer) / 2)
        }
    } catch {
        #it didn't work at this size, make buffer smaller
        Write-Host "$BufferSize" -ForegroundColor Red -NoNewline
        $LastMaxBuffer = $BufferSize
        #if we're getting close, just subtract 1
        if(($LastMaxBuffer - $LastMinBuffer) -le 3){
            $BufferSize = $BufferSize - 1
        } else {
            $BufferSize = $LastMinBuffer + (($LastMaxBuffer - $LastMinBuffer) / 2)
        }
    }
    Write-Host "," -ForegroundColor Gray -NoNewline
}
Write-Host "MTU: $BufferSize"


Remove Forefront Client Security by force

$
0
0

I was recently trying to upgrade the Antivirus software on some servers from Forefront Client Security to System Center Endpoint Protection 2012 R2. On most servers it worked fine. However on a few I was unable to remove some of the FCS components due to missing .msi files. This was because somebody (not me!) had been deleting the contents of the C:\Windows\Installer folder, probably to save disk space.

This meant that the uninstall command was failing as Windows Installer couldn’t find the right .msi file. This in turn meant I was unable to install SCEP as its installer check to see that all previous have gone before it’ll install.

There is a very faffy way of fixing missing MSIs where you have to track down the correct version of the .msi via the Microsoft Update Catalogue and/or a WSUS server, but it was too fiddly and I didn’t have time. Thus, I wrote a script to manually uninstall the old FCS stuff (or at least, enough of it that SCEP will install and be happy).

The script will continue on errors, and you will get errors as some of the stuff only relates to 32-bit OS, and some only to 64-bit. Plus you might have already been able to remove some of the FCS components properly via Windows Installer.

Here’s the script, save it as a .cmd file and run as administrator.

@echo off
echo Stop services
net stop MOM
net stop FCSAM
net stop FcsSas

echo Delete services
sc delete MOM
sc delete FCSAM
sc delete FcsSas

echo Kill GUI
tskill msascui /a

echo Delete files
rd /s /q "C:\Program Files\Microsoft Forefront"
rd /s /q "C:\Program Files (x86)\Microsoft Forefront"

Echo Remove registry keys
echo ...MOM
rem 64-bit
reg delete "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{F692770D-0E27-4D3F-8386-F04C6F434040}" /f
reg delete "HKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft Operations Manager\2.0" /f
rem 32-bit
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F692770D-0E27-4D3F-8386-F04C6F434040}" /f
rem both
reg delete "HKLM\SOFTWARE\Classes\Installer\Products\D077296F72E0F3D438680FC4F6340404" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\D077296F72E0F3D438680FC4F6340404" /f

echo ...SAS
rem 64-bit
reg delete "HKLM\SOFTWARE\Classes\Installer\Products\E4EB3435742B0D148BD1E4C755649001" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\E4EB3435742B0D148BD1E4C755649001" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{5343BE4E-B247-41D0-B81D-4E7C55460910}" /f
rem 32-bit
reg delete "HKLM\SOFTWARE\Classes\Installer\Products\838A5BA2CAD95F54E82C10D9DD4C4B6F" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\838A5BA2CAD95F54E82C10D9DD4C4B6F" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{2AB5A838-9DAC-45F5-8EC2-019DDDC4B4F6}" /f

echo ...FCS
rem 64-bit
reg delete "HKLM\SOFTWARE\Classes\Installer\Products\EE98922AA7EA8F240A0CC999FC6B44BF" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\EE98922AA7EA8F240A0CC999FC6B44BF" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{A22989EE-AE7A-42F8-A0C0-9C99CFB644FB}" /f
rem 32-bit
reg delete "HKLM\SOFTWARE\Classes\Installer\Products\FF0CF4D4791FF10448E21E811F2D46E7" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\FF0CF4D4791FF10448E21E811F2D46E7" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{4D4FC0FF-F197-401F-842E-E118F1D2647E}" /f
rem both
reg delete "HKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft Forefront" /f
reg delete "HKLM\SOFTWARE\Microsoft\Microsoft Forefront" /f

echo Done.
pause

Use with care.


Send Windows Event Logs to SQL Database

$
0
0

I’m currently in the process of planning for an AppLocker rollout to all my PCs (about 7,500 of them) due to an increasing amount of malware. You should probably be doing this too. Anyway, a sensible first step is to identify which paths things are running from, which is pretty easy – you just turn AppLocker on in Audit mode. This makes it write messages into its event log telling what has been allowed to run, and what would be blocked from running were it in enforce mode rather than audit mode.

You now have your PCs collecting all this useful info in their event logs. Now you need to collate it centrally and process it. “Aha”, I thought, “this will be a great time to try out using the built in Windows Event Forwarding“. I followed some instructions, and it worked fine on my Windows 8.1 PC, and also on a colleague’s Windows 10 PC. Sadly, it failed on the other 7498 Windows 7 PCs. After a few days of trying to get it to work on them, I gave up and wrote my own version in PowerShell.

I think my version is better, because it allows you to query events in SQL, which is easier for me than trying to extract sensible information directly out of an event log – especially if the info you’re after is in the Messages field. You could of course modify the script and the SQL table to collect whatever fields you want. This is also not restricted to the AppLocker event log, you can collect events from any Windows Event Log.

This is going to be a big post with multiple sections, sorry about that, but it is pretty straightforward.

Overview

  • You need an SQL server to store the events that you’re going to collect from the PCs. Maybe use SQL Server 2014 Express – which is free.
  • The PCs push the events to the SQL server using an SQL bulk copy, which is pretty efficient.
  • The event collection script is written in PowerShell, you should probably have at least version 3 of this on your PCs, and .Net 3.5, ideally PowerShell 4 and .Net 4.5 (at time of writing).
  • The collection script is launched via a scheduled task, runs as the Network Service account, does not show on the user’s desktop whilst running, and only requires the Domain Computers group to have access to the SQL database. I’ve configured the scheduled task via a Group Policy Preference.
  • The script writes a registry marker when it runs, and on subsequent runs only uploads events that have occurred since its last run. This means you can run it as often as you like an not get duplicate events in your SQL table.
  • The script takes two parameters, the event log name to collect from, and the SQL server to send the events to.

PowerShell Script

param(
    [parameter(Mandatory=$true)][string]$LogName,
    [parameter(Mandatory=$true)][string]$SQLServer
)

# Check event log for events written since this script was last run
# or all events if this is the first run of the script
# and then upload them to SQL Server efficiently

# Create a simplified version of the log name for use elsewhere in the script
$LogNameSimplified = $LogName.Replace("/","_")
$LogNameSimplified = $LogNameSimplified.Replace(" ","")
$LogNameSimplified = $LogNameSimplified.Replace("-","")
Write-Host "SQL table name: $LogNameSimplified"

# Registry key to store last run date & time
$RegKey = "HKCU:\Software\RCMTech\EventCollector"
# SQL Database that holds the table for the events
$SQLDatabase = "EventCollection2"

function Get-UserFromSID ($SID){
    # Does what it says on the tin
    $SIDObject = New-Object -TypeName System.Security.Principal.SecurityIdentifier($SID)
    $User = $SIDObject.Translate([System.Security.Principal.NTAccount])
    $User.Value
}

# Initialise LastRun variable, make it old enough that all events will be collected on first run
# Always use ISO 8601 format
[datetime]$LastRunExeDll = "1977-01-01T00:00:00"

if(Test-Path $RegKey){
    # Registry key exists, check LastRun value
    $LastRunValue = (Get-ItemProperty -Path $RegKey -Name $LogNameSimplified -ErrorAction SilentlyContinue).$LogNameSimplified
    if($LastRunValue -ne $null){
        $LastRunExeDll = $LastRunValue
    }
}else{
    # Registry key does not exist, create it, then set the NewsID value and run full script
    Write-Host "Registry key not present"
    New-Item -Path $RegKey -Force | Out-Null
}

# Get the events logged since LastRun date & time
Write-Host ("Collecting events from "+(Get-Date -Date $LastRunExeDll -Format s))
$Events = Get-WinEvent -FilterHashtable @{logname=$LogName; starttime=$LastRunExeDll} -ErrorAction SilentlyContinue
Write-Host ("Found "+$Events.Count+" events")

if($Events.Count -gt 0){
    # Process event data into a DataTable ready for upload to SQL Server
    # Create DataTable
    $DataTable = New-Object System.Data.DataTable
    $DataTable.TableName = $LogNameSimplified
    # Define Columns
    $Column1 = New-Object system.Data.DataColumn TimeCreated,([datetime])
    $Column2 = New-Object system.Data.DataColumn MachineName,([string])
    $Column3 = New-Object system.Data.DataColumn UserId,([string])
    $Column4 = New-Object system.Data.DataColumn Id,([int])
    $Column5 = New-Object system.Data.DataColumn Message,([string])
    # Add the Columns
    $DataTable.Columns.Add($Column1)
    $DataTable.Columns.Add($Column2)
    $DataTable.Columns.Add($Column3)
    $DataTable.Columns.Add($Column4)
    $DataTable.Columns.Add($Column5)
    # Add event data to DataTable
    foreach($Event in $Events){
        $Row = $DataTable.NewRow()
        $Row.TimeCreated = $Event.TimeCreated
        $Row.MachineName = $Event.MachineName
        $Row.UserId = Get-UserFromSID -SID $Event.UserId
        $Row.Id = $Event.Id
        $Row.Message = $Event.Message
        $DataTable.Rows.Add($Row)
    }

    # Bulk copy the data into SQL Server
    try{
        $SQLConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=$SQLServer;Integrated Security=SSPI;Database=$SQLDatabase"
        $SQLConnection.Open()
        $SQLBulkCopy = New-Object -TypeName System.Data.SqlClient.SqlBulkCopy -ArgumentList $SQLConnection
        $SQLBulkCopy.DestinationTableName = "dbo.$LogNameSimplified"
        $SQLBulkCopy.BulkCopyTimeout = 60
        $SQLBulkCopy.WriteToServer($Datatable)
        # Create/update the LastRun value - assuming all the above has worked - in ISO 8601 format
        New-ItemProperty -Path $RegKey -Name $LogNameSimplified -Value (Get-Date -Format s) -Force | Out-Null
        Write-Host "Data uploaded to SQL Server"
    }
    catch{
        Write-Host "Problem uploading data to SQL Server"
        Write-Error $error[0]
    }
}

A few points to note on the script:

  • Pass it the name of the event log to collect events from, e.g. Microsoft-Windows-AppLocker/EXE and DLL
  • If the upload to SQL Server fails, the timestamp marker is not written to the registry and thus the events in the event log will try to be uploaded again on the next run of the script. i.e. you will not be missing events in the SQL table if the SQL server is unavailable when the script runs.
  • The log name is simplified to a form that SQL Server is happy with by removing spaces and hyphens, and converting forward slashes to underscores.
  • The script assumes the database name of EventCollection
  • The script requires you to create a table within this database for each log that you want to collect from, the table name needs to be the simplified version of the log name passed to the script, e.g.
    Microsoft-Windows-AppLocker/EXE and DLL becomes
    MicrosoftWindowsAppLocker_EXEandDLL
  • See below for SQL script to create the database and table
  • ALWAYS use ISO 8601 format datetime with PowerShell! (especially if you live outside USA)
  • I have created a GP Pref to copy the script onto the C drive of all my PCs
  • Storing the event data in a DataTable is bit more fiddly than using a simple array of objects, but it makes the bulk copy into SQL Server much easier – you just dump the whole thing across.
  • The UserId returned from the event log in in the form of a SID, which is not terribly useful to me, so I wrote the Get-UserFromSID function to change this into a username.

SQL Database and Table Creation

Here’s the SQL Server code, paste into SQL Management Studio and run it.

USE [master]
GO

CREATE DATABASE [EventCollection]
 CONTAINMENT = NONE
 ON  PRIMARY
( NAME = N'EventCollection', FILENAME = N'D:\EventCollection.mdf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON
( NAME = N'EventCollection_log', FILENAME = N'L:\EventCollection_log.ldf' , FILEGROWTH = 10%)
GO

ALTER DATABASE [EventCollection] SET COMPATIBILITY_LEVEL = 110
GO

IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [EventCollection].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO

USE [EventCollection]
GO

CREATE TABLE [dbo].[MicrosoftWindowsAppLocker_EXEandDLL](
	[TimeCreated] [datetime] NULL,
	[MachineName] [varchar](50) NULL,
	[UserId] [varchar](50) NULL,
	[Id] [int] NULL,
	[Message] [varchar](500) NULL
) ON [PRIMARY]

GO

You also need to grant the Domain Computers group permission to bulk copy into the table.

From SQL Management Studio:

  1. Go to Security – Logins
  2. Create a new Login for the Domain Computers group, on the User Mapping page tick the EventCollection database.
  3. Go to Databases – EventCollection – Security – Users
  4. Double-click the Domain Computers group, go to the Securables page
  5. Click Search… – All objects of the types… – OK
  6. Tick Tables – OK
  7. Ensure the table is selected in the Securables section, then in the Explicit permission tab tick:
    1. Insert: Grant
    2. Select: Grant
  8. Click OK.

Note that I am not a SQL Server expert, so whilst this all works it may be missing many optimisations. I believe SQL bulk copies do not cause much transaction log activity, but you could always set your database to simple recovery mode anyway.

Scheduled Task Configuration

I’ve configured this via a Group Policy Preference. Here’s what I did.

  1. Create a new GPO, or edit an existing one. In the Group Policy Management Editor go to Computer Configuration, Preferences, Control Panel Settings, Scheduled Tasks.
  2. Right-click, New – Scheduled Task (At least Windows 7). Leave the settings at their defaults except as detailed below.
  3. General tab
    1. Action: Replace
    2. Name: Collect AppLocker Events EXE DLL
    3. When running the task, use the following user account: NT AUTHORITY\Network Service
  4. Triggers tab – you can use any trigger you like, personally I’m doing it once a day based on time and day of the week
    1. Click New…
    2. Begin the task: On a schedule
    3. Settings: Weekly
    4. Start: <today’s date> 15:00:00
    5. Recur every: 1 weeks on: Monday Tuesday Wednesday Thursday Friday
    6. Delay task for up to (random delay): 1 hour (stops your SQL server being overwhelmed with all the collections happening at once)
    7. Stop task if it runs longer than: 30 minutes (this is just a safety net in case the script errors badly/hangs)
    8. Enabled needs to be ticked
  5. Actions tab
    1. Click New…
    2. Action: Start a program
    3. Program/script: %WindowsDir%\System32\WindowsPowerShell\v1.0\powershell.exe (note that GPPrefs use their own “environment variables, hence %WindowsDir% and not %WinDir%. Hit F3 to view & insert GPPref variables)
    4. Add arguments(optional): -ExecutionPolicy Bypass -File “C:\Program Files\RCMTech\CollectEvents.ps1” (see my note earlier about using a GPPref to copy the script locally onto the PCs)
  6. Settings tab
    1. Allow task to be run on demand: ticked (useful for testing, and why not anyway)
    2. Run task as soon as possible after a scheduled start is missed: ticked (in case the PC is switched off when the task is scheduled to run)
  7. Common tab
    1. Remove this item when it is no longer applied: ticked

Note that running a scheduled task as a specific user is no longer possible via GPPref due a security flaw. Network Service is a good choice in this situation anyway. It causes the connection to the SQL server to be using the credentials of the computer’s own Active Directory account, e.g. RCMTech\MYPC$ which means you don’t need to give your users access to the database. This is good from a data protection point of view as the resulting database contains personally identifiable information.

Group Policy Preference to copy script onto target machines

  1. Computer Configuration, Preferences, Windows Settings, Files.
  2. Right-click, New – File
  3. General tab
    1. Source File(s): \\rcmtech.co.uk\NETLOGON\LocalScripts\*.*
    2. Destination Folder: %ProgramFilesDir%\RCMTech
    3. Suppress errors on individual file actions: ticked
  4. Common tab
    1. Remove this item when it is not longer needed: ticked
    2. Item-level targeting:
      1. the folder \\rcmtech.co.uk\NETLOGON\LocalScripts exists

All done

Once all the above is in place, you’re good to go. Now you just need to do something with all that event data sat in your SQL database.


Send notification email from SCCM 2012 Task Sequence

$
0
0

I wanted to be able to send an email from a step in a SCCM 2012 OS Deployment (OSD) task sequence. This was so that I could be notified when the task sequence had completed successfully.

The account used to send the email needed to be a specific account, as otherwise Exchange would reject the message. I’d already created a sccm.notifications@rcmtech.co.uk account in Active Directory and a mailbox for it in Exchange.

I thought I’d just use a Run Command Line step, and run PowerShell.exe specifying a very basic script file on the command line containing a Send-MailMessage cmdlet. That fails to run with an error though, and the Run PowerShell Script step doesn’t have a “Run this step as the following account” option.

So I reverted to “good old” VBScript. This is a script that sends an email using CDO.Message:

Const cdoNTLM = 2 'NTLM
Set oMessage = CreateObject("CDO.Message")
Dim sComputerName, sOSDComputerName, sSubject, sTextBody
Dim oShell
Set oShell = CreateObject("WScript.Shell")
sSubject = "Build Complete %COMPUTERNAME%"
sSubject = oShell.ExpandEnvironmentStrings(sSubject)
oMessage.Subject = sSubject
sTextBody = "Build Complete "&amp;Now
oMessage.TextBody = sTextBody
oMessage.From = "sccm.notifications@rcmtech.co.uk"
oMessage.To = "robin@rcmtech.co.uk"
oMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
oMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "mail.rcmtech.co.uk"
oMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
oMessage.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = cdoNTLM
oMessage.Configuration.Fields.Update
oMessage.Send

Save that into a file called EmailBuildComplete.vbs.

I then put this script file into an SCCM Package, and distributed it to my distribution points. At the end of the OSD task sequence, I then added a Run Command Line step called Send Email Notification, containing the following command line:

cscript.exe //nologo EmailBuildComplete.vbs

and set the step to run as my sccm.notifications account.


Viewing all 27 articles
Browse latest View live