Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
0.1 What Purple Teaming Is
Introduction
The attacker leads, even in the first lesson. A purple team runs an attacker's procedure on purpose, in the open, and then measures what the defense did with every step of it.
This course teaches it from both seats at once: the operator who runs the procedure and the defender who reads what it left, working one exercise with one record. A test is not finished when the procedure runs, or when a rule fires, but when both seats agree what happened at every step, all the way to the analyst's verdict.
This lesson shows what that means on a single recorded test. PT-0103 created a scheduled task from the command line on Tuesday 15 September, the engagement's first day. The rule meant for it alerted six minutes later, and nobody opened an incident. Every later module of the course is a larger version of reading that one test properly.
The course is written for defenders: SOC analysts, detection engineers and incident responders who want to know whether their detections work, and for the people who run the tests that tell them. It assumes you can read a query and know what an alert is. It does not assume you have run an attack before; the operator's seat is taught from the first module.
Each lesson in this module works the same way. It starts from something the operator did, follows the defender through the records it left, and ends with a case you work yourself in the course's console, where every query runs against the recording. The queries are short and the results are small on purpose: the point of Module 0 is the way of reading, not the volume of data.
Plan the round
What a purple team test commits each seat toEvery test in this course starts from the same short agreement between the seats, written before anything runs:
The decision line is the course's habit in one sentence. A purple team that says "we tested scheduled tasks and they are covered" has asserted something; one that says "PT-0103 left four records, PU-R02 alerted at 09:46, and no incident was opened" has shown it, with three queries anyone can rerun. Every lesson trains the second way of speaking.
The agreement is written before the test, not after, because the outcome is easier to read honestly when both seats committed to what they would look for. An operator who decides afterward which records count, or a defender who decides afterward which alerts were meant for the test, can make almost any result look like a pass. Writing the agreement first takes a minute and removes the temptation.
The last line matters as much. A test that raises no alert is a result, and often the most useful one, because it is the gap the exercise exists to find. A purple team that only reports the tests that alerted is reporting the defense's successes and hiding its failures from itself.
The attack: an Atomic test, run by its GUID
T1053.005 test 2, a scheduled task created from the command promptEvery test in this course starts in the operator's seat, with a procedure and the tool that runs it. The first is Atomic Red Team's T1053.005 test 2, Scheduled task Local, GUID 42f53695-ad4a-4546-abb6-7d837f644a71.
Atomic Red Team publishes small, documented procedures for ATT&CK techniques, each a YAML entry with a command, its inputs and its cleanup, so a test run today can be run again next month as exactly the same procedure. This one's executor is the command prompt:
SCHTASKS /Create /SC ONCE /TN spawn /TR #{task_command} /ST #{time}
It creates a task called spawn that would start cmd.exe once, at 20:10, which is the persistence an intruder wants: something Windows will run later on their behalf. Its cleanup deletes the task:
SCHTASKS /Delete /TN spawn /F >nul 2>&1
The operator runs it with Invoke-AtomicTest, the library's PowerShell runner: read it first, check what it needs, run it with the execution log on, and clean up.
PS C:\> $g = '42f53695-ad4a-4546-abb6-7d837f644a71'
PS C:\> Invoke-AtomicTest T1053.005 -TestGuids $g -ShowDetailsBrief
PS C:\> Invoke-AtomicTest T1053.005 -TestGuids $g -CheckPrereqs
PS C:\> $log = 'C:\PurpleArtifacts\art.csv'
PS C:\> Invoke-AtomicTest T1053.005 -TestGuids $g -ExecutionLogPath $log
PS C:\> Invoke-AtomicTest T1053.005 -TestGuids $g -Cleanup
Each line has a purpose. The brief details print the command the test will run, so the operator confirms it before anything executes. The prerequisite check runs nothing, and reports what is missing. The execution log is the tool's own record of the run, with its time, host, user and exit code, which the defender reads beside the test log.
The cleanup takes the same GUID, so it removes exactly what the test made. The course uses Atomic Red Team for most single procedures and MITRE Caldera for chained operations, where an agent on the host runs one ability after another. The test log row the operator wrote for this run, PT-0103:
PurpleTestLog_CL
| where TestId == "PT-0103"
| project TimeGenerated, TestId, Technique, AtomicTestName, TargetHost, Operator,
ExecutionStatus, CleanupTime
One row, the operator's account of the test:
Time TestId Technique Test Status
-------- ------- --------- -------------------- ---------
09:40:02 PT-0103 T1053.005 Scheduled task Local Completed
The row is a promise. It tells the defender what should be visible: a process on NE-PT-WS01, under pt.operator, at 09:40, creating a task. If the defender cannot find those records, either the test did not do what the row says or the defense cannot see it, and both are worth knowing.
Run it: what the host recorded
The records the test leftThe defender's first question is what the host recorded, before any rule is considered. Every process and device event on the workstation in the three minutes after the test started:
union
(DeviceProcessEvents | project Timestamp, Table = "DeviceProcessEvents",
What = FileName),
(DeviceEvents | project Timestamp, Table = "DeviceEvents",
What = ActionType)
| where Timestamp between (datetime(2026-09-15T09:40:00Z)
.. datetime(2026-09-15T09:43:00Z))
| order by Timestamp asc
Four records, the whole test from start to cleanup:
Time Table What
-------- ------------------- --------------------
09:40:02 DeviceProcessEvents cmd.exe
09:40:03 DeviceProcessEvents schtasks.exe
09:40:05 DeviceEvents ScheduledTaskCreated
09:41:32 DeviceEvents ScheduledTaskDeleted
cmd.exe started schtasks.exe, Defender recorded the task being created, and ninety seconds later recorded it being deleted by the cleanup. That is full visibility: every step the procedure took left a record. Whatever happens next, the defense had what it needed to notice the test.
Each record carries the account and the process that made it, so the defender can tie it to the operator's row without guessing. schtasks.exe ran as pt.operator, under cmd.exe, at the time the row gives. A record from another account, or another host, at the same minute would belong to something else, and the defender would leave it out of the test's evidence rather than count it.
Reading the records before the alerts is the course's second habit. It separates two very different gaps: a step that was recorded and not alerted, which a rule can fix, and a step that left no record, which no rule can fix until something records it. A purple team that starts from the alerts cannot tell them apart.
The variant: the same task, created through WMI
T1053.005 test 6, run two days later as PT-0303The operator reads the rule that is supposed to catch the test, and notices how narrowly it is written. PU-R02 as it stood on the first day:
DeviceProcessEvents
| where FileName =~ "schtasks.exe"
| where ProcessCommandLine has "/create"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
The rule reads schtasks.exe with "/create" on its command line. Windows will register the same task without schtasks.exe running at all, and Atomic Red Team has a test for it: T1053.005 test 6, WMI Invoke-CimMethod Scheduled Task, GUID e16b3b75-dc9e-4cde-a23d-dfa2d0507b3b, which hands the Task Scheduler's WMI class a task definition in XML:
$xml = [System.IO.File]::ReadAllText("#{xml_path}")
Invoke-CimMethod -ClassName PS_ScheduledTask `
-NameSpace "Root\Microsoft\Windows\TaskScheduler" `
-MethodName "RegisterByXml" `
-Arguments @{ Force = $true; Xml =$xml; }
The operator ran it on Thursday 17 September as PT-0303, with the same Invoke-AtomicTest steps. What the workstation recorded:
union
(DeviceProcessEvents | project T = Timestamp,
Record = strcat(FileName, " from ", InitiatingProcessFileName)),
(DeviceEvents | project T = Timestamp, Record = ActionType)
| where T between (datetime(2026-09-17T15:40:00Z) .. datetime(2026-09-17T15:44:00Z))
| order by T asc
The task created and deleted, and PowerShell's command, with no schtasks.exe anywhere:
Time Record
-------- --------------------
15:40:18 PowerShellCommand
15:40:19 ScheduledTaskCreated
15:42:18 PowerShellCommand
15:42:18 ScheduledTaskDeleted
Defender recorded the task, so the defense could have seen it, and PU-R02 had nothing to read: no alert followed. That is the conversation a purple team test starts. The operator's next move is always the procedure the rule does not read, and the defender's answer, a rule that reads the task itself rather than the tool that made it, comes from records like these.
Detection follows: the alert
What the rule noticed, and whenThe defense's rules run on schedules over the records. PU-R02 runs every five minutes. The alerts in the twenty minutes after the test:
SecurityAlert
| where TimeGenerated between (datetime(2026-09-15T09:40:00Z)
.. datetime(2026-09-15T10:00:00Z))
| project TimeGenerated, AlertName, AlertSeverity, CompromisedEntity
One alert, from the rule written for the test:
Time Alert Severity Host
-------- -------------------------------------------------- -------- ----------
09:46:02 PU-R02 Scheduled task created from the command ... Medium NE-PT-WS01
The alert names the host and the account, which is what lets the defender match it to the test. It does not name the test, because the rule has no idea a test was running; it saw a scheduled task created from the command line by a standard account and reported it.
That is the right behavior for a rule, and it is why the purple team, not the rule, is responsible for tying each alert to the procedure that caused it.
Six minutes from the test to the alert is the rule working as scheduled: its next run after the record arrived found it and alerted. A test that stopped here would be reported as detected, and most detection testing does stop here. A purple team does not, because an alert is not yet a defense; it is a message to someone who has to act on it.
The verdict: the incident nobody opened
What the defense did with the alertThe last thing the defender reads is what happened to the alert: an incident opened, investigated and closed with a verdict, or nothing. For PT-0103's alert the answer is nothing:
SecurityIncident
| where CreatedTime between (datetime(2026-09-15T09:40:00Z)
.. datetime(2026-09-15T10:00:00Z))
| count
The count is zero. Across the whole day the pattern is the same:
union
(SecurityAlert | where TimeGenerated between (datetime(2026-09-15)
.. datetime(2026-09-16)) | summarize N = count()
| extend What = "Alerts"),
(SecurityIncident | where CreatedTime between (datetime(2026-09-15)
.. datetime(2026-09-16)) | summarize N = count()
| extend What = "Incidents")
| project What, N
Six alerts on Tuesday, and one incident between them:
Record Count
--------- -----
Alerts 6
Incidents 1
The one incident that was opened, for a Run key test twenty minutes later, was closed as benign within half an hour, with a comment noting that purple team testing was running that week.
That verdict is reasonable, and it is also a warning: an analyst who knows a test is running may close any alert that looks like one, including a real intrusion that happens in the same week. The course comes back to that risk in Module 8.
Five of the day's six alerts were never turned into incidents, PT-0103's among them. The rule worked, the record was complete, and the defense still did nothing with the test.
That is the finding a purple team exists to produce, and it is not a detection finding at all: it is about how alerts reach the people who handle them. The course's later modules follow this exact gap to its repair.
What purple teaming is, and is not
Four kinds of test, four questionsThe word itself comes from mixing red and blue, the attacking and defending teams of an exercise, and it is sometimes used for any activity that puts them in a room together. This course means something narrower and more useful: a method in which both seats work one record and every test ends with a measured outcome, an owner and, where there is a gap, a repair and a retest.
Purple teaming is easiest to see beside the kinds of testing it is often confused with. Each answers a different question and changes something different:
Red team
Penetration test
Detection test
Purple team
A red team measures the defense without telling it and reports what an intruder achieved; it rarely fixes anything itself. A penetration test finds exploitable weaknesses for the system's owner. A detection test checks one rule against one procedure.
A purple team runs procedures in the open, measures every step against the whole defense, from records to rules to the analyst's verdict, and repairs what it finds before retesting it. It is the only one of the four whose last step is proving the fix.
The four are complementary rather than competing. A mature program uses red teams to find out what an intruder could do unannounced, penetration tests to find weaknesses before an intruder does, detection tests as part of writing rules, and purple teams to turn all of it into measured, repaired and retested defenses. This course teaches the last, and borrows from the others only where a purple team needs to.
That is also why this course does not teach exploitation or evasion. A purple team operator needs to run procedures exactly and record them, not to hide them; the procedures come from documented sources, and the skill is in choosing, running and reading them.
It also explains where the operator's procedures come from. Every test in the course is either a published Atomic Red Team test, identified by its GUID, a Caldera ability from MITRE's published library, or a custom procedure whose exact command is in the test log.
Nothing is improvised, and nothing is hidden from the defense, because a procedure the defense cannot see coming is a red team's tool, not a purple team's.
The two seats
What each does in every testBoth seats work every test, and the course teaches both in every lesson. What each owns:
Operator
Defender
The before column is where most of the value is decided. An operator who chooses a procedure without a source cannot repeat it; a defender who predicts no outcome cannot be surprised by one. Lesson 1.4 and the capstone's planning lesson both start here, and the course's first lab is an exercise charter written before any test runs.
Neither seat is the other's opponent. The operator wants the defense to improve and runs procedures that will show where it does not; the defender wants to know the truth about the rules and reads the records rather than the dashboard.
In a small team one person holds both seats, which is how you will work through most of this course, and the discipline is in keeping the two records apart: what ran, and what the defense did with it.
The seven measures
How far a test got through the defenseThe course measures every test on seven steps, from the procedure running to the defense responding. Each step has a short definition the whole course uses:
A test is classified by the last step it reached. PT-0103 on all seven:
Prevention is grey rather than red because no control was expected to stop a standard account creating a scheduled task; that is an ordinary thing for Windows to allow. The measures do not demand that every step be blocked, only that the plan say which steps should be, so a missing block is a finding only where one was expected.
The test reached the fifth step and stopped. A report that said "detected" would be true and would hide the two red steps, which are where the defense actually failed. Module 9 of the course builds the whole engagement's measurement on these seven steps, and every test you read before then is classified the same way.
Validate
The test by its alert, and by everything that followedThe obvious way to report PT-0103 is by its alert:
SecurityAlert
| where TimeGenerated between (datetime(2026-09-15T09:40:00Z)
.. datetime(2026-09-15T10:00:00Z))
| project TimeGenerated, AlertName, AlertSeverity, CompromisedEntityunion
(DeviceEvents | where ActionType == "ScheduledTaskCreated"
| project Timestamp, Record = "task created"),
(SecurityAlert | project Timestamp = TimeGenerated,
Record = "alert"),
(SecurityIncident | project Timestamp = CreatedTime,
Record = "incident")
| where Timestamp between (datetime(2026-09-15T09:40:00Z)
.. datetime(2026-09-15T10:00:00Z))
| order by Timestamp ascThe first try is not wrong, and that is what makes it dangerous. PU-R02 did fire, six minutes after the test, on the right host and account.
A dashboard counting detections would credit it, a coverage map would color the technique green, and the five unhandled alerts on the same day would never appear in either. The refined query costs two more lines and shows the step where the defense stopped.
The refined read is the one this course reports. On the chain both seats use, PT-0103 reads:
Outcome chain
PT-0103What PT-0103 shows
Agreed by both seats.
- The procedure ran as written and was cleaned up.
- Fully recorded and alerted; never investigated.
- A process gap, not a detection gap.
One test, read to the verdict: detected by the rule, unhandled by the defense.
The chain's owner row is worth noticing. The gap is not detection engineering's, because the rule worked; it belongs to whoever decides which alerts become incidents. Naming the right owner is half of fixing a gap, and a report that sent this one to the detection team would get a rule tuned that did not need tuning.
The chain is the course's shape for every result. You will fill it in for hundreds of steps by the capstone, and the habit of reading past the alert is the one this lesson exists to start.
Adapt: four outcomes the same test could have had
And what each would ask forPT-0103 had one outcome. The same test, on a different defense, could have had any of four:
Detected
Alerted, not handled
Recorded only
Not visible
None of the four is a pass or a fail on its own. A detected test with a forty-minute delay may be a failure for a step that needed stopping in five; a not-visible test may be acceptable for a host the plan said would not be monitored.
The outcome is a fact about the test, and whether it is good enough is a judgment the plan sets before the test runs.
Each outcome asks for different work from a different owner. Detected asks for nothing but a regression test. Alerted and not handled, PT-0103's outcome, goes to whoever runs the alert queue. Recorded only goes to detection engineering.
Not visible goes to the platform team, because no rule can read what is never recorded. Telling them apart is the skill the rest of the course builds, and it always starts from the records rather than the alerts.
Read one test from both seats
The case below follows PT-0103 from the operator's row to the day's incidents. Work in the platform's Advanced hunting console with the Purple Range selected; every query runs against the course's recording.
The case is short, four questions, and it is the same shape as every case in the course: one step in the operator's seat and the rest in the defender's, each answered from a query you can see, change and run again.
Work the case in order. Each step's answer is used by the next, and the order is the order the course always reads a test in: the operator's row, the host's records, the rule's alert, and the defense's verdict. If an answer surprises you, rerun its query and read the rows before moving on; the console shows the same recording every lesson reads.
The fourth answer is the one to remember: the rule worked and the defense still did nothing. Every module of the course is, in the end, about finding answers like that one and fixing them.
Practice
Read another test from both seats your labThe practice is in the console, against the recording; your own lab is built in Lessons 0.7 to 0.11.
Operator: the row
Open PT-0105, the service installed on Tuesday 15 September, and note its host, account, time and cleanup.
Defender: the records
Find its process and device records in the test's first five minutes.
Defender: the alert
Find the alert, if any, and the minutes from the test's start.
Defender: the verdict
Find the incident, if any, and read its comment.
Both: classify
The last of the seven measures the test reached, with the record that shows it.
What you should end up with: PT-0105 read on the seven measures, in the shape of this lesson's outcome chain.
Resources
Publishers revise these pages; where a page and this lesson disagree, the page is newer.