Le contrôle de la création de nouveaux comptes utilisateur est un aspect fondamental du maintien d’un environnement informatique sécurisé. Que vous protégiez un domaine Active Directory d’entreprise ou que vous gériez des postes de travail autonomes, la détection de la création d’utilisateurs non autorisés ou inattendus peut s’avérer essentielle pour prévenir les menaces internes, les erreurs de configuration ou même les violations externes. Cet article explore une solution basée sur PowerShell conçue pour alerter les professionnels de l’informatique lorsque de nouveaux comptes utilisateurs sont créés dans un délai donné, en fournissant une intégration optimale avec NinjaOne pour une visibilité centralisée.
Contexte
Dans les environnements d’entreprise, les administrateurs informatiques et les fournisseurs de services gérés (MSP) ont souvent du mal à suivre chaque changement de compte, en particulier dans les infrastructures hybrides. Des acteurs malveillants peuvent créer de nouveaux comptes en guise de portes dérobées, ou des changements légitimes peuvent passer inaperçus, laissant des failles de sécurité. Windows offre des possibilités d’audit par le biais des journaux d’événements et d’Active Directory, mais l’extraction et l’utilisation de ces données par programme n’est pas triviale.
Ce script PowerShell résout ce problème en automatisant la détection des nouveaux comptes utilisateurs et en poussant éventuellement les données d’alerte dans un champ personnalisé multiligne de NinjaOne. Il prend en charge à la fois les contrôleurs de domaine (DC) et les systèmes autonomes, ce qui le rend adaptable à divers environnements Windows. Ce script est un script PowerShell avancé d’alerte pour les nouveaux utilisateurs et un outil de conformité fiable pour l’audit et la surveillance.
Le script
#Requires -Version 5.1
<#
.SYNOPSIS
Finds and alerts on new user accounts created within a specified time frame, measured in minutes. If ran on a domain controller, it will use Active Directory to find new users, otherwise it will use the event log to find new users.
.DESCRIPTION
This script finds and alerts on new user accounts created within a specified time frame, measured in minutes.
It can be used to monitor user creation activity in an Active Directory environment or an individual system.
On a domain controller, it retrieves new users from Active Directory. If the Active Directory Recycle Bin is enabled, it will also retrieve deleted users that were created within the specified time frame.
On an individual system, it retrieves new users from the event log. The event log will not retain these events indefinitely.
When running on an individual system, it checks if auditing is enabled for User Account Management.
If auditing is not enabled, the following will need to be run in an elevated PowerShell/cmd session:
auditpol.exe /set /subcategory:{0CCE9235-69AE-11D9-BED3-505054503030} /success:enable
This will enable auditing for user account management events, including user creation required to track new user creation events.
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider.
Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
.PARAMETER TimeFrameInMinutes
The time frame in minutes to check for new users.
Minimum value is 5 minutes.
Maximum value is 525600 minutes (1 year).
.PARAMETER MultilineCustomFieldName
The name of the multiline custom field to set with the new user information.
This field will be populated with the names and creation times of the new users.
.EXAMPLE
-TimeFrameInMinutes 10000 -MultilineCustomFieldName "Multiline"
This example checks for new users created within the last 10000 minutes and sets the multiline custom field "Multiline" with the user information.
Example output:
[Alert] New users created within the last 10000 minutes:
[Alert] Username: test, Full Name: test test, Created On: 05/20/2025 13:13:35
[Alert] Username: anewaccount, Full Name: Another New Account, Created On: 05/20/2025 15:01:10
[Info] Attempting to set Custom Field 'Multiline'.
[Info] Successfully set Custom Field 'Multiline'!
.EXAMPLE
-TimeFrameInMinutes 2440
This example checks for new users created within the last 1440 minutes (24 hours).
Example output:
[Alert] New users created within the last 1440 minutes:
[Alert] Username: test, Full Name: test test, Created On: 05/20/2025 13:13:35
[Alert] Username: anewaccount, Full Name: Another New Account, Created On: 05/20/2025 15:01:10
.NOTES
Minimum OS Architecture Supported: Windows 10, Windows Server 2016
Release Notes: Initial release
#>
[CmdletBinding()]
param (
$TimeFrameInMinutes,
[string]$MultilineCustomFieldName
)
begin {
# Import the script variables
if ($env:timeFrameInMinutes) { $TimeFrameInMinutes = $env:timeFrameInMinutes }
if ($env:multilineCustomFieldName) { $MultilineCustomFieldName = $env:multilineCustomFieldName }
function Test-IsElevated {
[CmdletBinding()]
param ()
# Get the current Windows identity of the user running the script
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
# Create a WindowsPrincipal object based on the current identity
$p = New-Object System.Security.Principal.WindowsPrincipal($id)
# Check if the current user is in the Administrator role
# The function returns $True if the user has administrative privileges, $False otherwise
# 544 is the value for the Built In Administrators role
# Reference: https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsbuiltinrole
$p.IsInRole([System.Security.Principal.WindowsBuiltInRole]'544')
}
function Test-IsDomainJoined {
# Check the PowerShell version to determine the appropriate cmdlet to use
try {
if ($PSVersionTable.PSVersion.Major -lt 3) {
return $(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
}
else {
return $(Get-CimInstance -Class Win32_ComputerSystem).PartOfDomain
}
}
catch {
Write-Host -Object "[Error] Unable to validate whether or not this device is a part of a domain."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
}
function Test-IsDomainController {
# Determine the method to retrieve the operating system information based on PowerShell version
try {
$OS = if ($PSVersionTable.PSVersion.Major -lt 3) {
Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop
}
else {
Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
}
}
catch {
Write-Host -Object "[Error] Unable to validate whether or not this device is a domain controller."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
# Check if the ProductType is "2", which indicates that the system is a domain controller
if ($OS.ProductType -eq "2") {
return $true
}
}
function Set-NinjaPropertyValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)]
[String]$Name,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Value,
[Parameter()]
[String]$Type,
[Parameter()]
[String]$DocumentName,
[Parameter()]
[Switch]$Piped
)
if ($Type -eq "Date Time") { $Type = "DateTime" }
if ($Type -match "[-]") { $Type = $Type -replace '-' }
if ($Type -match "[/]") { $Type = $Type -replace '/' }
# Remove the non-breaking space character
if ($Type -eq "WYSIWYG") {
$Value = $Value -replace ' ', ' '
}
if ($Type -eq "DateTime" -or $Type -eq "Date") {
$Type = "Date or Date Time"
}
# Measure the number of characters in the provided value
$Characters = $Value | ConvertTo-Json | Measure-Object -Character | Select-Object -ExpandProperty Characters
# Throw an error if the value exceeds the character limit of 200,000 characters
if ($Piped -and $Characters -ge 200000) {
throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 200,000 characters.")
}
if (!$Piped -and $Characters -ge 45000) {
throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 45,000 characters.")
}
# Initialize a hashtable for additional documentation parameters
$DocumentationParams = @{}
# If a document name is provided, add it to the documentation parameters
if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
# Define a list of valid field types
$ValidFields = "Checkbox", "Date", "Date or Date Time", "DateTime", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine",
"MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
# Warn the user if the provided type is not valid
if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type. Please check here for valid types: https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
# Define types that require options to be retrieved
$NeedsOptions = "Dropdown", "MultiSelect"
# If the property is being set in a document or field and the type needs options, retrieve them
if ($DocumentName) {
if ($NeedsOptions -contains $Type) {
$NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
}
}
else {
if ($NeedsOptions -contains $Type) {
$NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
}
}
# Throw an error if there was an issue retrieving the property options
if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
# Process the property value based on its type
switch ($Type) {
"Checkbox" {
# Convert the value to a boolean for Checkbox type
$NinjaValue = [System.Convert]::ToBoolean($Value)
}
"Date or Date Time" {
# Convert the value to a Unix timestamp for Date or Date Time type
$Date = (Get-Date $Value).ToUniversalTime()
$TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
[long]$NinjaValue = $TimeSpan.TotalSeconds
}
"Dropdown" {
# Convert the dropdown value to its corresponding GUID
$Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
$Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
# Throw an error if the value is not present in the dropdown options
if (!($Selection)) {
throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown options.")
}
$NinjaValue = $Selection
}
"MultiSelect" {
$Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
$Selections = New-Object System.Collections.Generic.List[String]
if ($Value -match "[,]") {
$Value = $Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
}
$Value | ForEach-Object {
$GivenValue = $_
$Selection = $Options | Where-Object { $_.Name -eq $GivenValue } | Select-Object -ExpandProperty GUID
# Throw an error if the value is not present in the dropdown options
if (!($Selection)) {
throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown options.")
}
$Selections.Add($Selection)
}
$NinjaValue = $Selections -join ","
}
"Time" {
# Convert the value to a Unix timestamp for Date or Date Time type
$LocalTime = (Get-Date $Value)
$LocalTimeZone = [TimeZoneInfo]::Local
$UtcTime = [TimeZoneInfo]::ConvertTimeToUtc($LocalTime, $LocalTimeZone)
[long]$NinjaValue = ($UtcTime.TimeOfDay).TotalSeconds
}
default {
# For other types, use the value as is
$NinjaValue = $Value
}
}
# Set the property value in the document if a document name is provided
if ($DocumentName) {
$CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
}
else {
try {
# Otherwise, set the standard property value
if ($Piped) {
$CustomField = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1
}
else {
$CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
}
}
catch {
throw $_.Exception.Message
}
}
# Throw an error if setting the property failed
if ($CustomField.Exception) {
throw $CustomField
}
}
function Get-OSVersion {
# Get the OS version information
return [System.Environment]::OSVersion.Version
}
function Test-IsSystem {
[CmdletBinding()]
param ()
# Get the current Windows identity of the user running the script
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
# Check if the current identity's name matches "NT AUTHORITY*"
# or if the identity represents the SYSTEM account
return $id.Name -like "NT AUTHORITY*" -or $id.IsSystem
}
# If time frame in minutes exists, validate that it is an integer
# If it does not exist, error out because it is required
if (-not [string]::IsNullOrWhiteSpace($TimeFrameInMinutes)) {
# Convert the value to an integer
try {
$TimeFrameInMinutes = [int]$TimeFrameInMinutes
}
catch {
Write-Host -Object "[Error] Error while converting the value for 'Time Frame In Minutes' to an integer:"
Write-Host -Object "[Error] $($_.Exception.Message)"
Write-Host -Object "[Error] The value for 'Time Frame In Minutes' must be an integer between 5 and 525600."
exit 1
}
# Validate the TimeFrameInMinutes is within the valid range
if ($TimeFrameInMinutes -lt 5 -or $TimeFrameInMinutes -gt 525600) {
Write-Host -Object "[Error] An invalid value was provided for 'Time Frame In Minutes': '$TimeFrameInMinutes'"
Write-Host -Object "[Error] The value for 'Time Frame In Minutes' must be an integer between 5 and 525600."
exit 1
}
}
else {
Write-Host -Object "[Error] The value for 'Time Frame In Minutes' is required and must be provided."
exit 1
}
# Validate the custom field name if it exists
if ($MultilineCustomFieldName) {
# Error if the custom field name is empty
if ([string]::IsNullOrWhiteSpace($MultilineCustomFieldName)) {
Write-Host -Object "[Error] The value for 'Multiline Custom Field Name' cannot be empty."
exit 1
}
# Trim whitespace from the custom field name
$MultilineCustomFieldName = $MultilineCustomFieldName.Trim()
# Validate that the field name contains only alphanumeric characters
if ($MultilineCustomFieldName -match "[^0-9A-Z]") {
Write-Host -Object "[Error] The 'Multiline Custom Field Name' of '$MultilineCustomFieldName' is invalid as it contains invalid characters."
Write-Host -Object "[Error] Please provide a valid multiline custom field name to save the results, or leave it blank."
Write-Host -Object "[Error] https://ninjarmm.zendesk.com/hc/en-us/articles/360060920631-Custom-Field-Setup"
exit 1
}
}
# Get the OS version information
try {
$osVersion = Get-OSVersion -ErrorAction Stop
}
catch {
Write-Host -Object "[Error] Failed to retrieve the OS version information."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
# Check if the OS version is supported
if ($osVersion.Major -lt 10 -or ($osVersion.Major -eq 10 -and $osVersion.Build -lt 14393)) {
Write-Host -Object "[Error] This script requires Windows 10 or Windows Server 2016 or later."
exit 1
}
}
process {
# Check if the script is running with administrative privileges
if (-not (Test-IsElevated)) {
Write-Host -Object "[Error] This script requires administrative privileges. Please run as an administrator."
exit 1
}
# Error if not running as system
if (-not (Test-IsSystem)) {
Write-Host -Object "[Error] This script must be run as the SYSTEM account. Please run as the SYSTEM account."
exit 1
}
$ExitCode = 0
# Get the current date and time
try {
$currentDateTime = Get-Date -ErrorAction Stop
}
catch {
Write-Host -Object "[Error] Failed to retrieve the current date and time."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
try {
# Calculate the start date and time based on the specified time frame
$startDateTime = $currentDateTime.AddMinutes(-$TimeFrameInMinutes)
}
catch {
Write-Host -Object "[Error] Failed to calculate the start date and time."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
# Get the list of new users created within the specified time frame
# If host is a DC, get new users from Active Directory
# Otherwise, get new users from the event log
if (Test-IsDomainController) {
Write-Host -Object "`n[Info] This host is a domain controller. Checking for new users in Active Directory created within the last $TimeFrameInMinutes minutes."
$IsDomainController = $true
# Initialize a list to hold new users
$newUsers = [System.Collections.Generic.List[PSCustomObject]]::new()
# Check if the AD Recycle Bin is enabled
try {
$ADRecycleBinEnabled = (Get-ADOptionalFeature -Filter { Name -eq "Recycle Bin Feature" } -ErrorAction Stop).FeatureScope
}
catch {
Write-Host -Object "[Error] Failed to check if the AD Recycle Bin is enabled."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
if (-not $ADRecycleBinEnabled) {
Write-Host -Object "[Warning] The Active Directory Recycle Bin is not enabled. Deleted users cannot be tracked until the Recycle Bin is enabled. This may lead to less accurate results."
}
else {
try {
# If the Recycle Bin is enabled, try to find deleted users created within the time frame
$deletedADUsers = Get-ADObject -Filter {
ObjectClass -eq "User" -and
ObjectClass -ne "Computer" -and
IsDeleted -eq $TRUE -and
WhenCreated -ge $startDateTime
} -Properties SamAccountName, WhenCreated -IncludeDeletedObjects -ErrorAction Stop
# Format the deleted users to include only the necessary properties and add a status property
$deletedADUsers = $deletedADUsers | Select-Object Name,
SamAccountName,
@{
Name = "WhenCreated"
Expression = { $_.WhenCreated.ToShortDateString() + " " + $_.WhenCreated.ToLongTimeString() }
},
@{
Name = "Status"
Expression = { "Deleted" }
}
}
catch {
Write-Host -Object "[Error] Failed to retrieve deleted users from Active Directory."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
}
try {
# Find new users in AD created within the time frame
$newUsersInAD = Get-ADUser -Filter { WhenCreated -ge $startDateTime } -Properties WhenCreated -ErrorAction Stop
# Format new users in AD to include only the necessary properties and add a status property
$newUsersInAD = $newUsersInAD | Select-Object Name,
SamAccountName,
@{
Name = "WhenCreated";
Expression = { $_.WhenCreated.ToShortDateString() + " " + $_.WhenCreated.ToLongTimeString() }
},
@{
Name = "Status";
Expression = {
if ($_.Enabled) {
"Enabled"
}
else {
"Disabled"
}
}
}
}
catch {
Write-Host -Object "[Error] Failed to retrieve users from Active Directory."
Write-Host -Object "[Error] $($_.Exception.Message)"
exit 1
}
# Add each new user in AD to the list of new users
foreach ($user in $newUsersInAD) {
$newUsers.Add($user)
}
# Add each deleted AD user that was created within the time frame to the list of new users
foreach ($user in $deletedADUsers) {
# Modify the name of the user to only include the first line before adding it to the list
$user.Name = $user.Name -split "`n" | Select-Object -First 1
$newUsers.Add($user)
}
}
else {
Write-Host -Object "`n[Info] Checking for new local users created within the last $TimeFrameInMinutes minutes in the event log."
# Warn if domain-joined that only local users will be included
if (Test-IsDomainJoined) {
Write-Host -Object "`n[Warning] This host is domain-joined but not a domain controller. Only local users will be included in the results."
Write-Host -Object "[Warning] Please run this script on a domain controller if you'd like to alert on domain user accounts."
}
# Check if user account creation auditing is enabled
$AuditPolicy = (Get-ItemProperty HKLM:\Security\Policy\PolAdtEv\ -ErrorAction SilentlyContinue)."(default)"
if ($AuditPolicy) {
# If enabled, the value of this should be 1 (success only) or 3 (success and failure)
$UserCreationAuditingValue = $AuditPolicy[102]
if ($UserCreationAuditingValue -ne 1 -and $UserCreationAuditingValue -ne 3) {
Write-Host -Object "[Error] Auditing is not enabled for User Account Management. Please enable it to track new user creation events."
Write-Host -Object "[Info] After auditing is enabled, only new user creation events after the change will be tracked."
Write-Host -Object "[Info] To enable auditing, run the following command in an elevated PowerShell/cmd session:"
Write-Host -Object "[Info] auditpol.exe /set /subcategory:'{0CCE9235-69AE-11D9-BED3-505054503030}' /success:enable"
exit 1
}
}
else {
Write-Host -Object "[Error] Failed to check if auditing is enabled for User Account Management. This is usually due to the script not running as the SYSTEM account."
exit 1
}
# Get new users from the event log
# Event ID 4720 corresponds to "A user account was created"
$eventLog = @(Get-WinEvent -FilterHashtable @{LogName = "Security"; Id = 4720; StartTime = $startDateTime } -ErrorAction SilentlyContinue)
# Process each event log entry to extract the user information
$newUsers = foreach ($event in $eventLog) {
# Convert the event to an XML object
$eventXML = [xml]$event.ToXml()
# Extract the username from the XML
$userName = $eventXml.Event.EventData.Data | Where-Object { $_.Name -eq "TargetUserName" } | Select-Object -ExpandProperty '#text'
# If a username is found, attempt to retrieve the local user's full name from their username
# Otherwise, error and continue to the next event log entry
if ($userName) {
try {
$localUser = Get-LocalUser -Name $UserName -ErrorAction Stop
# Determine the status of the user account
$status = if ($localUser.Enabled) { "Enabled" } else { "Disabled" }
if (-not [string]::IsNullOrWhiteSpace($localUser.FullName)) {
$fullName = $localUser.FullName
}
elseif (-not [string]::IsNullOrWhiteSpace($localUser.Name)) {
$fullName = $localUser.Name
}
else {
$fullName = "Unknown"
}
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException] {
# If the user is not found, it may have been deleted or renamed, so set the status to "No Longer Exists"
$fullName = "Unknown"
$Status = "No Longer Exists"
}
catch {
Write-Host -Object "[Error] Unable to retrieve the full name of the user '$userName'."
Write-Host -Object "[Error] $($_.Exception.Message)"
$fullName = "Unknown"
$Status = "Unknown"
$ExitCode = 1
}
# Create a custom object with the user information
[PSCustomObject]@{
UserName = $userName
Name = $fullName
Status = $status
WhenCreated = $event.TimeCreated.ToShortDateString() + " " + $event.TimeCreated.ToLongTimeString()
}
}
else {
Write-Host -Object "[Error] Failed to extract the username from the event data."
$ExitCode = 1
continue
}
}
}
# If new users are found not from AD, filter out any duplicate users in the list, keeping the most recently created instance only
if ($newUsers.Count -gt 1 -and -not $IsDomainController) {
# Group the users by username and select the most recent entry for each group
$newUsers = $newUsers | Group-Object -Property UserName | ForEach-Object {
$_.Group | Sort-Object -Property WhenCreated -Descending | Select-Object -First 1
}
}
# Check if any new users were found
if ($newUsers) {
Write-Host -Object "`n[Alert] New users have been created within the last $TimeFrameInMinutes minutes:`n"
# Format the new users object for output, showing the Enabled accounts first, then the Disabled accounts
$newUsers = $newUsers | Sort-Object -Property { $_.Status -eq "Enabled"; $_.Status -eq "Disabled" }, WhenCreated -Descending | Select-Object @{
Name = "Username"
Expression = { if ($IsDomainController) { $_.SamAccountName } else { $_.UserName } }
}, @{
Name = "Full Name"
Expression = { $_.Name }
}, @{
Name = "Created On"
Expression = { $_.WhenCreated }
}, Status
# Format it as a list and trim the whitespace
$newUsers = ($newUsers | Format-List | Out-String).Trim()
# Output to the activity feed
$newUsers | Out-Host
}
elseif ($MultilineCustomFieldName) {
Write-Host -Object "[Info] No new users have been created within the last $TimeFrameInMinutes minutes. The custom field '$MultilineCustomFieldName' will not be modified."
}
else {
Write-Host -Object "[Info] No new users have been created within the last $TimeFrameInMinutes minutes."
}
# Write to custom field if there are new users
if ($newUsers -and $MultilineCustomFieldName) {
try {
Write-Host -Object "`n[Info] Attempting to set the custom field '$MultilineCustomFieldName'."
Set-NinjaPropertyValue -Name $MultilineCustomFieldName -Value $newUsers -Type "MultiLine"
Write-Host -Object "[Info] Successfully set the custom field '$MultilineCustomFieldName'!"
}
catch {
Write-Host -Object "[Error] Error setting the custom field '$MultilineCustomFieldName'."
Write-Host -Object "[Error] $($_.Exception.Message)"
$ExitCode = 1
}
}
exit $ExitCode
}
end {
}
Description détaillée
Voici comment fonctionne le script, divisé en étapes logiques :
1. Validation des entrées
- Accepte deux paramètres :
TimeFrameInMinutes: La fenêtre de temps à vérifier (de 5 minutes à 1 an).MultilineCustomFieldName: Le champ personnalisé NinjaOne pour stocker les résultats (facultatif).
- Effectue des vérifications strictes concernant la configuration minimale du système d’exploitation (Windows 10/Server 2016), la version de PowerShell, le formatage approprié et les autorisations (doit être exécuté en tant que SYSTEM et avec des droits d’administrateur).
2. Détection de l’environnement
- Détermine si le système :
- Est relié à un domaine.
- Est un contrôleur de domaine (DC).
- L’audit de la gestion des comptes d’utilisateurs est activé (important pour les non-DC).
- En fonction de l’environnement, il choisit entre Active Directory et les journaux d’événements locaux comme source de données.
3. Détection d’un nouvel utilisateur
- Mode de contrôleur de domaine:
- Recherche dans Active Directory tous les utilisateurs créés après la date de début calculée
date de début. - Vérifie éventuellement la corbeille AD pour les utilisateurs récemment créés et supprimés.
- Recherche dans Active Directory tous les utilisateurs créés après la date de début calculée
- Mode autonome/non DC :
- Lit le journal des événements de Windows Security pour l’ID d’événement 4720 (création d’un compte d’utilisateur).
- Extrait les noms d’utilisateur et les enrichit avec des détails provenant de
Get-LocalUser.
4. Formatage et enregistrement des données
- Les résultats sont présentés sous forme d’objets propres et lisibles :
- Nom d’utilisateur
- Nom complet
- Le temps de la création
- Statut du compte (activé, désactivé, supprimé)
5. Intégration de NinjaOne
- Si un champ personnalisé valide est fourni, il utilise la fonction
Set-NinjaPropertyValuepour enregistrer les données dans la plateforme NinjaOne. - Veille à ce qu’aucune modification ne soit apportée aux champs personnalisés si aucun utilisateur n’est trouvé.
Cas d’utilisation potentiels
Étude de cas : Environnements du domaine de surveillance MSP
Imaginez qu’une entreprise MSP gère un domaine pour un client du secteur financier. Il déploit ce script pour qu’il s’exécute toutes les heures sur tous les contrôleurs de domaine via NinjaOne. Un jour, le script les avertit qu’un utilisateur nommé jsmith_temp a été créé au cours des 30 dernières minutes. Cette situation était inattendue et, après enquête, ils ont découvert qu’un outil d’automatisation des ressources humaines avait mal fonctionné pendant l’onboarding. Sans ce script, ce compte aurait pu passer inaperçu et présenter un risque potentiel.
Comparaisons
| Méthode | Avantages | Inconvénients |
| Ce script PowerShell | Journalisation centralisée, intégration de NinjaOne, prise en charge à la fois les DC et en local | Nécessite une exécution au niveau du système, une configuration pour l’audit |
| Surveillance manuelle du journal des événements | Natif, aucun script n’est nécessaire | Nécessite beaucoup de main-d’œuvre, manque d’automatisation |
| Outils SIEM (par exemple, Splunk) | Évolutif, facile à corréler | Configuration coûteuse et complexe |
| Alertes électroniques AD | Simple | Pas de suivi historique, manque de formatage |
En comparaison, le script PowerShell offre une solution intermédiaire abordable et extensible, idéale pour les MSP et les entreprises de taille moyenne.
Questions fréquentes
Question 1 : Dois-je l’exécuter en tant que SYSTÈME ?
Oui. Ce script nécessite des autorisations de niveau SYSTÈME pour accéder aux journaux d’événements et aux champs personnalisés de NinjaOne.
Question 2 : Que se passe-t-il si l’audit n’est pas activé ?
Sur les systèmes non CD, le script vérifie et vous indique comment activer l’audit à l’aide de auditpol.exe.
Question 3 : Fonctionne-t-il sur les machines d’un groupe de travail ?
Oui, tant que l’audit est activé et que le système répond aux exigences du système d’exploitation.
Question 4 : Puis-je programmer cela avec NinjaOne ?
Absolument. Il est conçu pour fonctionner via le module de script de NinjaOne avec des paramètres et peut envoyer des alertes vers des champs personnalisés pour faciliter la création de rapports.
Implications
L’exécution régulière de ce script PowerShell introduit une surveillance proactive des comptes dans votre dispositif de sécurité. L’identification des créations de comptes inattendues peut aider à prévenir l’escalade des privilèges, à maintenir la conformité (HIPAA, SOX, etc.) et à soutenir les audits internes. Lorsqu’il est intégré à NinjaOne, ce script permet une visibilité centralisée et cohérente sur les appareils et les locataires, ce qui permet aux MSP de réagir plus rapidement et en toute confiance.
Recommandations
- Exécuter en tant que SYSTÈME: Veillez à exécuter le script avec les privilèges SYSTÈME, ce qui est particulièrement important pour accéder aux journaux de sécurité.
- Paramètres d’audit: Pour obtenir des données fiables, activez toujours l’audit de la gestion des comptes d’utilisateurs sur les systèmes locaux.
- Validation des champs: Utilisez uniquement des noms alphanumériques pour les champs personnalisés de NinjaOne afin d’éviter les problèmes de format.
- Ajustement du cadre temporel: Ajuster
TimeFrameInMinutesen fonction de la fréquence du script : par exemple 1440 pour une fréquence quotidienne, 60 pour une fréquence horaire. - Gestion des erreurs : Surveillez les journaux de sortie du script à la recherche de
[Erreur]pour détecter les erreurs de configuration.
Conclusion
La création d’un nouveau compte utilisateur est une opération sensible qui nécessite visibilité et contrôle. Ce script PowerShell offre un moyen intelligent et fiable de créer des alertes de nouvel utilisateur avec un minimum de frais généraux. Associé à NinjaOne, il devient encore plus puissant : il permet d’obtenir des informations exploitables et des historiques sur l’ensemble de votre infrastructure. Que vous soyez un administrateur informatique solitaire ou que vous gériez une équipe au sein d’une entreprise MSP, cette solution vous aide à garder une longueur d’avance en matière de sécurité des comptes utilisateurs.