PI Services

Le blog des collaborateurs de PI Services

Powershell : Trouver la date de dernière connexion d'un utilisateur.

Comme vous le savez l'un des attributs qui nous est le plus important pour déterminer l'inactivité d'un compte est l'attribut "LastLogon", ce dernier permet de connaitre la dernière authentification valide d'un compte Active Directory.

Le problème c'est que ce dernier n'est pas répliqué entre les DCs, attention je parle bien du "LastLogon" et non le "LastLogonTimeStamp" qui lui l'est mais, ne détient pas la valeur réelle de la dernière authentification réussie.

Le "LastLogon" n'est pas répliqué entre les DCs pour une raison simple, éviter les "tempêtes" de réplication entre les DCs, imaginez, à chaque ouverture / déverrouillage de session cette valeur change, si vous pensez au nombre de fois que sont déverrouillées des sessions à la journée, cela vous donne une idée des réplications qui seraient nécessaires, par conséquent cet attribut n'est pas répliqué.

Ce qui pose donc problème c'est qu'il n'est pas possible d'avoir la réelle valeur de "LastLogon" sans devoir interroger l'intégralité des DCs et faire une comparaison de cette dernière.

Voici un script qui fera les requêtes et comparaisons pour vous, toutefois gardez à l'esprit qu'il va pour chaque utilisateur demander la valeur de l'ttribut "LastLogonà chaque DC, par conséquent ce script peut prendre plus ou moins de temps à s'exécuter en fonction du nombre d'utilisateur et du nombre de DCs.

$Array = @()
$AllDC = Get-ADDomainController -Filter * | sort Name | select Name
$AllEnabledUsers = Get-ADUser -Filter {Enabled -eq $true} | select Samaccountname

$AllEnabledUsers | select -First 5 | foreach {
    $CurrentUser = $_.Samaccountname
    $RealLastLogon = $null

    $AllDC | foreach {
        $CurrentDC = $_.Name

        Try {
            $Logon = Get-ADUser $CurrentUser -Properties LastLogon -Server $CurrentDC
            $LastLogon = [Datetime]::FromFileTime($Logon.LastLogon)
            If ($LastLogon -gt $RealLastLogon) {
                $RealLastLogon = $LastLogon
                }

            # Release variable
            $LastLogon = $null
            }
        Catch {
            Write-Warning $($_)
            }

        # Release variable
        $CurrentDC = $null
        }
    
    # Store Data
    $Array += New-Object psobject -Property @{
        DistinguishedName = $Logon.DistinguishedName
        Enabled = $Logon.Enabled
        GivenName = $Logon.GivenName
        Name = $Logon.Name
        ObjectClass = $Logon.ObjectClass
        ObjectGUID = $Logon.ObjectGUID
        SamAccountName = $Logon.SamAccountName
        SID = $Logon.SID
        Surname = $Logon.Surname
        UserPrincipalName = $Logon.UserPrincipalName
        LastLogon = $RealLastLogon
        }
        
    # Release Variables
    $CurrentUser = $null
    $RealLastLogon = $null
    }

Il ne vous restera qu'a examiner la sortie en rappelant la variable "$Array" ou à l'exporter dans un CSV comme ci-dessous par exemple.

$Array | Export-Csv C:\Temp\AllUsersWithLastLogon.csv -Delimiter ";" -Encoding UTF8 -NoTypeInformation

 

Si la valeur "LastLogon" est égale à "01/01/1601 01:00:00" cela veut dire que le compte ne s'est jamais connecté

Script - API Vmware VCenter - Get VM infos

Le script ci-dessous est un exemple simple de l'utilisation de l'API Rest de Vcenter pour recuperer toute sorte d'information, nottament sur les machines virtuelles.

A noter que la limitation du nombre d'objet retournés oblige simplement a recuperer dans un premier temps la liste des hosts, puis par host la liste des VMs.

 

### QUERY VCENTER REST API TO GET VM LIST ###


$user = ‘myaccount’
$pswd = Read-Host -Prompt "Enter Password"
$vCenterName = ‘MyVcenter’
$encoded = [System.Text.Encoding]::UTF8.GetBytes(($user, $pswd -Join ‘:’))
$encodedPassword = [System.Convert]::ToBase64String($Encoded)
$authHeader = @{
Authorization = "Basic $($EncodedPassword)"
}
$sRest = @{
Method = ‘Post’
Uri = "https://$($vCenterName)/rest/com/vmware/cis/session"
Headers = $authHeader
}
$result = Invoke-RestMethod @sRest


# Get TokenID
$authHeader = @{
‘vmware-api-session-id’ = $result.value
}


# Get All Hosts
$gethosts = "https://$($vCenterName)/rest/vcenter/host"
$resultgethost = Invoke-RestMethod -Uri $gethosts -Headers $authHeader
$hostidlist = $resultgethost.value.host


# For each host, get all VMs

foreach ($hostid in $hostidlist)
{
$get_vm = "https://$($vCenterName)/rest/vcenter/vm?filter.hosts=$hostid"
$resultvm = Invoke-RestMethod -Uri $get_vm -Headers $authHeader
[array]$FinalTableau += $resultvm
}

# Display Result
$FinalTableau.value | select name,power_state


# Output to CSV
$CsvTab = $FinalTableau.value | select name,power_state | ConvertTo-Csv -Delimiter ';' -NoTypeInformation
$CsvTab | Out-File .\VMList.csv

 

 

 

 

SCCM - SQL Queries - Deployments,Assignments,Updates

Ci-dessous, une série de requêtes SQL sur les déploiements, les assignations et les mise a jours.

/**** QUERIES: DEPLOYMENTS,ASSIGNMENTS,UPDATES ****/


--- LIST OF DEPLOYMENTS NAME
SELECT 
--DeploymentID = cia.Assignment_UniqueID,
DeploymentName = isnull(grp.Title+' - ', '') + cia.AssignmentName
FROM [dbo].[vCI_CIAssignments]  CIA
LEFT JOIN [dbo].[vCI_AssignmentTargetedGroups]  ATG on CIA.AssignmentType=5 and ATG.AssignmentID=CIA.AssignmentID
LEFT JOIN [dbo].[v_AuthListInfo] GRP on CIA.AssignmentType=5 and GRP.CI_ID=ATG.AssignedUpdateGroup
WHERE CIA.AssignmentType in (1,5)




--- LIST OF ASSIGNMENTS NAME WITH RELATED DEPLOYMENTS NAME
SELECT 
--AssignmentID,
CIA.AssignmentName,
--,DEPLOY.DeploymentID,
DEPLOY.DeploymentName 

FROM v_CIAssignment CIA
INNER JOIN (
			SELECT DeploymentID = cia.Assignment_UniqueID
			, DeploymentName = isnull(grp.Title+' - ', '') + cia.AssignmentName
			FROM [dbo].[vCI_CIAssignments]  CIA
			LEFT JOIN[dbo].[vCI_AssignmentTargetedGroups]  ATG on CIA.AssignmentType=5 and ATG.AssignmentID=CIA.AssignmentID
			LEFT JOIN [dbo].[v_AuthListInfo] GRP on CIA.AssignmentType=5 and GRP.CI_ID=ATG.AssignedUpdateGroup
			WHERE CIA.AssignmentType in (1,5)

			) DEPLOY on DEPLOY.DeploymentID = CIA.Assignment_UniqueID



---- LIST OF UPDATES 

		SELECT DISTINCT 
		--CI_UniqueID,
		DisplayTitle = ui.Title+isnull(' ('+nullif(ui.ArticleID, '')+')', '') + isnull(' ('+nullif(ui.BulletinID, '') +')', '')
		,DateCreated
		, (CASE ui.Severity 
		WHEN 0 THEN 'NONE' 
		WHEN 2 THEN 'LOW' 
		WHEN 6 THEN 'MODERATE' 
		WHEN 8 THEN 'IMPORTANT' 
		WHEN 10 THEN 'CRITICAL' END) AS SEVERITY
		FROM v_UpdateInfo UI
		
		ORDER BY DateCreated DESC

        

---- LIST OF UPDATES AND RELATED ASSIGNMENTS NAME

		SELECT DISTINCT 
		--CI_UniqueID,
		DisplayTitle = ui.Title+isnull(' ('+nullif(ui.ArticleID, '')+')', '') + isnull(' ('+nullif(ui.BulletinID, '') +')', '')
		,DateCreated
		,(CASE ui.Severity 
			WHEN 0 THEN 'NONE' 
			WHEN 2 THEN 'LOW' 
			WHEN 6 THEN 'MODERATE' 
			WHEN 8 THEN 'IMPORTANT' 
			WHEN 10 THEN 'CRITICAL' END) AS SEVERITY
		,CIA.AssignmentName
		FROM v_CIAssignment CIA
		INNER JOIN v_CIAssignmentToCI  ATC on ATC.AssignmentID=CIA.AssignmentID
		INNER JOIN v_UpdateInfo UI on UI.CI_ID=ATC.CI_ID

		ORDER BY DateCreated DESC
		
		



---- LIST OF UPDATES AND RELATED DEPLOYMENTS NAME

		SELECT 
		--CI_UniqueID,
		DisplayTitle = ui.Title+isnull(' ('+nullif(ui.ArticleID, '')+')', '') + isnull(' ('+nullif(ui.BulletinID, '') +')', '')
		,UI.DateCreated
		,(CASE ui.Severity 
			WHEN 0 THEN 'NONE' 
			WHEN 2 THEN 'LOW' 
			WHEN 6 THEN 'MODERATE' 
			WHEN 8 THEN 'IMPORTANT' 
			WHEN 10 THEN 'CRITICAL' END) AS SEVERITY
		,DEPLOY.DeploymentName
		FROM v_CIAssignment CIA
		INNER JOIN v_CIAssignmentToCI  ATC on ATC.AssignmentID=CIA.AssignmentID
		INNER JOIN v_UpdateInfo UI on UI.CI_ID=ATC.CI_ID
		INNER JOIN (
					SELECT DeploymentID = cia.Assignment_UniqueID
					, DeploymentName = isnull(grp.Title+' - ', '') + cia.AssignmentName
					FROM [dbo].[vCI_CIAssignments]  CIA
					LEFT JOIN [dbo].[vCI_AssignmentTargetedGroups]  ATG on CIA.AssignmentType=5 and ATG.AssignmentID=CIA.AssignmentID
					LEFT JOIN [dbo].[v_AuthListInfo] GRP on CIA.AssignmentType=5 and GRP.CI_ID=ATG.AssignedUpdateGroup
					WHERE CIA.AssignmentType in (1,5)

					) DEPLOY on DEPLOY.DeploymentID = CIA.Assignment_UniqueID
			
		ORDER BY DateCreated DESC

	

 

 

Script - Invoquer un update d'un agent Trend OSC a distance

Le script ci-dessous propose d'utiliser a distance la commande "PccNTMon.exe -u" sur un agent Trend OSC pour invoquer une mise a jour, en direction de son serveur OSC de rattachement.

 

#### RemoteInvokeTrendOSCAgentUpdate.ps1: REMOTELY INITIATE AN UPDATE OF A TREND OSC AGENT ####

## COMMENT: The script execute command through PSSession and open a RDP window to allow interactive use of "OSCAgentExe -u" ###

param(
# Target Computer
$Computer = "MyTarget",
# Cred
$Cred = $(Get-Credential -Credential "MyAccount"),
# OSC Agent Folder
$OSCAgentFolder = "C:\Program Files (x86)\Trend Micro\OfficeScan Client\",
# OSC Client Process
$OSCClientProc = "PccNTMon"
)


# OSC Agent Exe
$OSCAgentExe = "$OSCAgentFolder$OSCClientProc`.exe"


# FUNCTIONS
function Set-WindowState {
	
	[CmdletBinding(DefaultParameterSetName = 'InputObject')]
	param(
		[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
		[Object[]] $InputObject,

		[Parameter(Position = 1)]
		[ValidateSet('FORCEMINIMIZE', 'HIDE', 'MAXIMIZE', 'MINIMIZE', 'RESTORE',
					 'SHOW', 'SHOWDEFAULT', 'SHOWMAXIMIZED', 'SHOWMINIMIZED',
					 'SHOWMINNOACTIVE', 'SHOWNA', 'SHOWNOACTIVATE', 'SHOWNORMAL')]
		[string] $State = 'SHOW'
	)

	Begin {
		$WindowStates = @{
			'FORCEMINIMIZE'		= 11
			'HIDE'				= 0
			'MAXIMIZE'			= 3
			'MINIMIZE'			= 6
			'RESTORE'			= 9
			'SHOW'				= 5
			'SHOWDEFAULT'		= 10
			'SHOWMAXIMIZED'		= 3
			'SHOWMINIMIZED'		= 2
			'SHOWMINNOACTIVE'	= 7
			'SHOWNA'			= 8
			'SHOWNOACTIVATE'	= 4
			'SHOWNORMAL'		= 1
		}

		$Win32ShowWindowAsync = Add-Type -MemberDefinition @'
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
'@ -Name "Win32ShowWindowAsync" -Namespace Win32Functions -PassThru

		if (!$global:MainWindowHandles) {
			$global:MainWindowHandles = @{ }
		}
	}

	Process {
		foreach ($process in $InputObject) {
			if ($process.MainWindowHandle -eq 0) {
				if ($global:MainWindowHandles.ContainsKey($process.Id)) {
					$handle = $global:MainWindowHandles[$process.Id]
				} else {
					Write-Error "Main Window handle is '0'"
					continue
				}
			} else {
				$global:handle = $process.MainWindowHandle
			}

			$Win32ShowWindowAsync::ShowWindowAsync($handle, $WindowStates[$State]) | Out-Null
			Write-Verbose ("Set Window State '{1} on '{0}'" -f $handle, $State)
		}
	}
}




# FOR EACH COMPUTER
foreach ($Comp in $Computer) {


# Create PSSession
try
{
New-PSSession -ComputerName $Comp -Credential $Cred -Name "PSSession_$Comp" -ErrorAction Stop
}
catch
{
write-host -F Red "Error during New-PSSession `n $($Error[0].Exception)"
exit 1 
}


# Get PSsession
$RemoteSession = Get-PSSession -Name "PSSession_$Comp"


# Open RDP Session (needed because the invoke update is interactive)
cmdkey /generic:TERMSRV/$Comp /user:($Cred.UserName) /pass:($Cred.GetNetworkCredential().Password)

mstsc /v:$Comp /w:50 /h:50

Write-Host "Wait for 10 sec..."
Start-Sleep -Seconds 10


# Get the RDP process Id to kill it at the end
$ProcId = (Get-Process mstsc | Where-Object {$_.MainWindowTitle -like "*$Comp*"}).Id


# Minimize RDP Window
(Get-Process mstsc | Where-Object {$_.MainWindowTitle -like "*$Comp*"}) | foreach {Set-WindowState $_ HIDE}


Write-Host "Wait for 10 sec..."
Start-Sleep -Seconds 10



# Invoke Update
write-host "Invoking update..."
Invoke-Command -Session $RemoteSession -ScriptBlock {Start-Process -FilePath $Using:OSCAgentExe -ArgumentList "-u"} 


        # Wait while update (see for update of ofcscan.ini file)
        do {
            Write-Host "Wait while update..."
           }

        until

           (
            (Invoke-Command -Session $RemoteSession -ScriptBlock {Get-ChildItem -Path "$Using:OSCAgentFolder`ofcscan.ini" | Where-Object {$_.LastWriteTime -gt $(get-date).AddMinutes(-15)}})
           )


Write-Host "Wait for 10 sec..."
Start-Sleep -Seconds 10



# Close RDP Session
write-host "Logoff RDP Session on $Comp..."
Invoke-Command -Session $RemoteSession -ScriptBlock {`
    $CredUser = $($Using:Cred.UserName)
    $sessions = quser | Where-Object {$_ -match $CredUser} ; 
    $sessionIds = ($sessions -split ' +')[2] ; 
    Write-Host "Found $(@($sessionIds).Count) user login(s) on computer.";
    $sessionIds | ForEach-Object {Write-Host "Logging off session id [$($_)]..." ; logoff $_}
     
     }


# Close RDP Session Disconnect Window by killing process
Get-Process -Id $ProcId | stop-process


}

# Remove Pssession
Get-PSSession | Remove-PSSession

 

Powershell : Supprimer les KB "Declined"

Quand on veut faire du ménage sur son WSUS et également dans la base de ce dernier, il peut s'avérer utile de supprimer les KB dont le statut est "IsDeclied".

Pour réaliser cela rien de plus simple que quelques lignes de Powershell et de la patience (beaucoup de patience si ça fait longtemps que cela n'a pas été fait).

Connexion

# Define Server Name
$WsusServer = "Mon-Server-WSUS"
# Define connection port
$WsusPort = "8530"

# Initiate Connection
[void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
$Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$false,$WsusPort)

# Verifying connection (Should be return the name of the wsus server)
$Wsus.name

 

Définition des Updates à supprimer

# List All Kb in WSUS database
$AllKb = $Wsus.GetUpdates()

# Filter on "IsDeclined" status
$IsDeclined = $AllKb.Where({$_.IsDeclined -eq $True})

 

Suppression des KB

# Delete KB
$IsDeclined | foreach {
    # Define Current Id
    $Id = $_.Id
    Try {
        $Wsus.DeleteUpdate($Id.UpdateId.ToString())
        }
    Catch {
        Write-Warning $($_)
        }
    # Release variable
    $Id = $null
    }

Cette commande est un peu longue mais si elle est exécutée régulièrement, la purge dure moins longtemps.

Powershell : Change the owner of my AD objects

Comme évoqué dans l'article  "Powershell : Who's the owner of my AD objects", il est possible que votre AD contienne des objets dont le propriétaire ne soit aucun des suivants :

  • "Enterprise Admins"
  • "Domain Admins"
  • "Administrators"

Afin de remettre "Domain Admins" comme propriétaire des objets, nous utiliserons Powershell pour chacun des types d'objets.

Attention : La variable $NoGood est issue des scripts du précédent article, pensez à vérifier ce qu'elle retourne au préalable.

Unités d'organisation

# Organizational Units
$NoGood | foreach {
    # Current OU
    $DistinguishedName = $_.DistinguishedName

    # Change Owner
    Try {
        # Define Target
        $TargetObject = Get-ADOrganizationalUnit -Identity $DistinguishedName
        $AdsiTarget = [adsi]"LDAP://$($TargetObject.DistinguishedName)"

        # Set new Owner
        $NewOwner = New-Object System.Security.Principal.NTAccount("ASSYSTEM", "Domain Admins")
        $AdsiTarget.PSBase.ObjectSecurity.SetOwner($NewOwner)
        $AdsiTarget.PSBase.CommitChanges()
        }
    Catch {
         Write-Warning $($_)
         $DistinguishedName
        }

    # Release variable
    $DistinguishedName = $null
    }

Les groupes

# Group
$NoGood | foreach {
    # Current Group
    $SamAccountName = $_.SamAccountName

    # Change Owner
    Try {
        # Define Target
        $TargetObject = Get-ADGroup $SamAccountName
        $AdsiTarget = [adsi]"LDAP://$($TargetObject.DistinguishedName)"

        # Set new Owner
        $NewOwner = New-Object System.Security.Principal.NTAccount("ASSYSTEM", "Domain Admins")
        $AdsiTarget.PSBase.ObjectSecurity.SetOwner($NewOwner)
        $AdsiTarget.PSBase.CommitChanges()
        }
    Catch {
        Write-Warning $($_)
        $SamAccountName
        }
    
    # Release variable
    $SamAccountName = $null
    }

Les utilisateurs

# Users
$NoGood | foreach {
    # Current User
    $SamAccountName = $_.SamAccountName

    # Change Owner
    Try {
        # Define Target
        $TargetObject = Get-ADUser $SamAccountName
        $AdsiTarget = [adsi]"LDAP://$($TargetObject.DistinguishedName)"

        # Set new Owner
        $NewOwner = New-Object System.Security.Principal.NTAccount("ASSYSTEM", "Domain Admins")
        $AdsiTarget.PSBase.ObjectSecurity.SetOwner($NewOwner)
        $AdsiTarget.PSBase.CommitChanges()
        }
    Catch {
        Write-Warning $($_)
        $SamAccountName
        }
    
    # Release variable
    $SamAccountName = $null
    }

 

Les ordinateurs

# Computers
$NoGood | foreach {
    # Current  Computer
    $SamAccountName = $_.SamAccountName

    # Change Owner
    Try {
        # Define Target
        $TargetObject = Get-ADComputer $SamAccountName
        $AdsiTarget = [adsi]"LDAP://$($TargetObject.DistinguishedName)"

        # Set new Owner
        $NewOwner = New-Object System.Security.Principal.NTAccount("ASSYSTEM", "Domain Admins")
        $AdsiTarget.PSBase.ObjectSecurity.SetOwner($NewOwner)
        $AdsiTarget.PSBase.CommitChanges()
        }
    Catch {
        Write-Warning $($_)
        $SamAccountName
        }

    # Release variable
    $SamAccountName = $null
    }

 

O365 : Forcer le changement de mot de passe

Il arrive dans certain cas de figure que nous souhaitions forcer le changement de mot de passe des comptes O365.

S'il s'agit d'un seul utilisateur voici la commande :

Set-MsolUserPassword -UserPrincipalName jdupont@mondomaine.com -ForceChangePasswordOnly $true -ForceChangePassword $true

Prenez soin de modifier le Userprincipalname de la commande sous peine d'obtenir une erreur.

 

En revanche si l'on veut forcer l'intégralité de la société à changer de mot de passe cela va nécessiter quelques lignes de plus.

# Variables
$LogFolder = "C:\temp"
$LogFile = "$LogFolder\LogO365_Reset.txt"
$LogFileError = "$LogFolder\LogO365_Reset_Error.txt"

# check
If (!(Test-Path $LogFolder)) {
    New-Item $LogFolder -ItemType Directory
    }

# Exceptions
$FilteredUsers = "Userprincipalname1", "Userprincipalname2", "Userprincipalname3" # Ajouter les UPN à ne pas reset (exemple  le compte Admin du Tenant)

# Connect to O365
Connect-MsolService

# Get users
$AllUsers = Get-MsolUser -MaxResults 10000 | select UserPrincipalName

# Force to change Password
$AllUsers | foreach {
    $UPN = $_.UserPrincipalName
    If ($FilteredUsers -eq $UPN) {
        Write-Host "Do not reset Password For $UPN because we had filtered him" | Add-Content $LogFile
        }
    Else {
        Try {
            Set-MsolUserPassword -UserPrincipalName $UPN -ForceChangePasswordOnly $true -ForceChangePassword $true
            Write-Output "Succesfully reset Password For $UPN" | Add-Content $LogFile
            }
        Catch {
            $Date = Get-Date
            Write-Warning "At $Date the following Error Appear $($_)" | Add-Content $LogFileError
            }
        }
    }

 

Bon courage au service IT pour le nombre d'appel qu'il va recevoir. 

 

O365 : Réaliser un hard match

 

Comme expliqué dans l'article précédent "O365 : Soft Match (SMTP) et Hard Match (ImmutableID)" le Hard Match intervient lorsque le Soft Match n'a pas fonctionné.

Les étapes à suivre sont les suivantes:

  1. Récupérer le GUID du compte dans l'Active Direcotry
  2. Convertir ce dernier en ImmutableID
  3. Appliqué cet ImmutableID au compte Azure Active Directory
  4. Relancer une synchronisation via Ad connect 

Voici donc les commandes pour cela.

# Get GUID for User
$User = Get-ADUser jdupont | select ObjectGUID,UserPrincipalName
$Upn = $User.UserPrincipalName
$Guid = $User.ObjectGUID.Guid

# Convert GUID to ImmutableID
$ImmutableId = [System.Convert]::ToBase64String(([GUID]($User.ObjectGUID)).tobytearray())

# Connect MsolService
Connect-Msolservice

# Set ImmutableID to msoluser
Set-MsolUser -UserPrincipalName $Upn -ImmutableId $ImmutableId

 

Et voila, vous n'avez plus qu'a relancer une Synchor AD Connect via la commande suivante.

Start-ADSyncSyncCycle -PolicyType Delta

 

 

Powershell : Who's the owner of my AD objects

Lors de la création d'un objet Active Directory, un propriétaire est inscrit dans l'attribut "Security Descriptor" ce dernier dépend de "qui" a créé le compte.

 

  • Dans un AD sans délégation de droits d'administration.

Si l'utilisateur qui créé l'objet est membre du groupe :

  • "Enterprise Admins" et "Domain Admins" : Le propriétaire sera le groupe "Domain Admins"
  • "Domain Admins" : Le propriétaire sera le groupe "Domain Admins"

  • "Enterprise Admins" : Le propriétaire sera le groupe "Enterprise Admins"

  • Dans un AD avec délégation de droits d'administration.

Lorsque l'on délègue les droits de création d'objets dans Active Directory, le compte utilisé pour créer des objets sera définit comme propriétaire de ces derniers.

Si par exemple je délègue la création d'objets de type "Users" au groupe "SG-Users-Management" et que le membre "Thomas DUPONT" créé un utilisateur, ce dernier sera propriétaire de l'objet utilisateur.

Si demain "Thomas DUPONT" n'est plus membre du groupe (pour n'importe quelle raison), il ne sera plus en mesure de créer / Modifier / Supprimer d'utilisateurs (jusque la tout va bien c'est ce que l'on désire), MAIS (et oui il y en a toujours un) ce ne sera pas vrai pour tous les objets créés par "Thomas DUPONT" précédemment.

Pourquoi ?

Et bien simplement parce que ne l'oublions "Thomas DUPONT" est le propriétaire des objets qu'il a créé, par conséquent il possède par défaut le droit de lecture et modification de l'objet.

Et donc ?

Ne l'oublions pas, le droit "Modify" permet de gérer les autorisations sur un objet, par conséquent il est donc possible pour "Thomas DUPONT" de se donner un droit "Full Control" sur ces objets.

Mais alors comment on identifie ces objets ?

Voici comment vérifier qui est le propriétaire des objets présents dans Active Directory.

  • Dans Active Directory Users and Computers

Comme nous l'avons vu plus haut il est possible via la console Active Directory Users and Computers de voir qui est le propriétaire d'un objet en passant par l'onglet "Security" puis "Advanced", mais bon pour un objet à la limite mais on va pas passer tous les objets à la main, c'est la qu'entre en jeu la seconde solution "Powershell".

  • Avec powershell

 Avec Powershell, il suffit d'utiliser la commande "Get-Aduser" et de récupérer la propriété "nTSecurityDescriptor

$User = Get-ADUser mlemoine -Properties nTSecurityDescriptor

 Ok, mais on en fais quoi de ça me direz vous, c'est pas faux ça nous aide pas trop, alors complétons la commande comme suivant :

$User = Get-ADUser mlemoine -Properties nTSecurityDescriptor
$User.nTSecurityDescriptor.owner

C'est déjà mieux, mais si on vérifiait tout l'AD.

# Create Array
$Array = @()

# Collect AD infos
$Domain = Get-ADDomain | select -ExpandProperty NetBIOSName
$AllUsers = Get-ADUser -Filter * -Properties nTSecurityDescriptor

# Store Info
$AllUsers | foreach {
    $DistinguishedName = $_.DistinguishedName
    $GivenName = $_.GivenName
    $Name = $_.Name
    $ObjectClass = $_.ObjectClass
    $ObjectGUID = $_.ObjectGUID
    $SamAccountName = $_.SamAccountName
    $SID = $_.SID
    $Surname = $_.Surname
    $UserPrincipalName = $_.UserPrincipalName
    $nTSecurityDescriptor = $_.nTSecurityDescriptor
    
    
    $Array += New-Object psobject -Property @{
        DistinguishedName = $DistinguishedName
        GivenName = $GivenName
        Name = $Name
        Owner = $nTSecurityDescriptor.owner
        ObjectClass = $ObjectClass
        ObjectGUID = $ObjectGUID
        SamAccountName = $SamAccountName
        SID = $SID
        Surname = $Surname
        UserPrincipalName = $UserPrincipalName
        }
    
    $DistinguishedName = $null
    $GivenName = $null
    $Name = $null
    $nTSecurityDescriptor = $null
    $ObjectClass = $null
    $ObjectGUID = $null
    $SamAccountName = $null
    $SID = $null
    $Surname = $null
    $UserPrincipalName = $null
    $CurrentUser = $null
    $nTSecurityDescriptor = $null
    }

Bon maintenant qu'on a stocké les donnés, il faudrait les exploiter :

# How many Accounts were returns ?
Write-Host $Array.Count -ForegroundColor Yellow

# How many Accounts need to be reviewed ?
$NoGood = $Array.Where({(($_.Owner -ne "$Domain\Domain Admins") -and ($_.Owner -ne "$Domain\Enterprise Admins") -and ($_.Owner -ne "BUILTIN\Administrators"))})
Write-Host $NoGood.count -ForegroundColor Cyan

Apparement j'ai 12 comptes sur les 6031 de mon AD qui n'ont pas "Domain Admins", "Enterprise Admins" ou "Administrators" comme Owner, voyons voir cela.

$NoGood | ft Name,Owner

Ah, attention on voit bien que qu'il y a des objets liés à Exchange, je vais donc ajouter un filtre.

$Filter = $NoGood.Where({$_.Name -notlike "HealthMailbox*"})

Et voilà, je n'ai plus qu'un objet, super.

Il est possible d'appliquer la même mécanique aux éléments ci-dessous en modifiant la requête :

  • Unités d'Organisation
  • Groupes
  • Ordinateurs

 Unités d'Organisation

# Create Array
$Array = @()

# Collect AD infos
$Domain = Get-ADDomain | select -ExpandProperty NetBIOSName
$AllOU = Get-ADOrganizationalUnit -Filter * -Properties nTSecurityDescriptor

# Store Info
$AllOU | foreach {
    $City = $_.City
    $Country = $_.Country
    $DistinguishedName = $_.DistinguishedName
    $ManagedBy = $_.ManagedBy
    $Name = $_.Name
    $nTSecurityDescriptor = $_.nTSecurityDescriptor
    
    
    $Array += New-Object psobject -Property @{
        City = $City
        Country = $Country
        DistinguishedName = $DistinguishedName
        ManagedBy = $ManagedBy
        Name = $Name
        Owner = $nTSecurityDescriptor.owner
        }
    
    $City = $null
    $Country = $null
    $DistinguishedName = $null
    $ManagedBy = $null
    $Name = $null
    $nTSecurityDescriptor = $null
    }

# How many Accounts were returns ?
Write-Host $Array.Count -ForegroundColor Yellow

# How many Accounts need to be reviewed ?
$NoGood = $Array.Where({(($_.Owner -ne "$Domain\Domain Admins") -and ($_.Owner -ne "$Domain\Enterprise Admins") -and ($_.Owner -ne "BUILTIN\Administrators"))})
Write-Host $NoGood.count -ForegroundColor Cyan

 

Groupes

# Create Array
$Array = @()

# Collect AD infos
$Domain = Get-ADDomain | select -ExpandProperty NetBIOSName
$AllGroups = Get-ADGroup -Filter * -Properties nTSecurityDescriptor

# Store Info
$AllGroups | foreach {
    $DistinguishedName = $_.DistinguishedName
    $GroupCategory = $_.GroupCategory
    $GroupScope = $_.GroupScope
    $Name = $_.Name
    $ObjectClass = $_.ObjectClass
    $ObjectGUID = $_.ObjectGUID
    $SamAccountName = $_.SamAccountName
    $SID = $_.SID
    $nTSecurityDescriptor = $_.nTSecurityDescriptor
    
    
    $Array += New-Object psobject -Property @{
        DistinguishedName = $DistinguishedName
        GroupCategory = $GroupCategory
        GroupScope = $GroupScope
        Name = $Name
        ObjectClass = $ObjectClass
        ObjectGUID = $ObjectGUID
        SamAccountName = $SamAccountName
        SID = $SID
        Owner = $nTSecurityDescriptor.owner
        }
    
    $DistinguishedName = $null
    $DNSHostName = $null
    $Enabled = $null
    $Name = $null
    $ObjectClass = $null
    $ObjectGUID = $null
    $SamAccountName = $null
    $SID = $null
    $nTSecurityDescriptor = $null
    }

# How many Accounts were returns ?
Write-Host $Array.Count -ForegroundColor Yellow

# How many Accounts need to be reviewed ?
$NoGood = $Array.Where({(($_.Owner -ne "$Domain\Domain Admins") -and ($_.Owner -ne "$Domain\Enterprise Admins") -and ($_.Owner -ne "BUILTIN\Administrators"))})
Write-Host $NoGood.count -ForegroundColor Cyan

 

Ordinateurs

# Collect AD infos
$Domain = Get-ADDomain | select -ExpandProperty NetBIOSName
$AllComputers = Get-ADComputer -Filter * -Properties nTSecurityDescriptor

# Store Info
$AllComputers | foreach {
    $DistinguishedName = $_.DistinguishedName
    $GroupCategory = $_.GroupCategory
    $GroupScope = $_.GroupScope
    $Name = $_.Name
    $ObjectClass = $_.ObjectClass
    $ObjectGUID = $_.ObjectGUID
    $SamAccountName = $_.SamAccountName
    $SID = $_.SID
    $nTSecurityDescriptor = $_.nTSecurityDescriptor
    
    
    $Array += New-Object psobject -Property @{
        DistinguishedName = $DistinguishedName
        DNSHostName = $DNSHostName
        Enabled = $Enabled
        Name = $Name
        ObjectClass = $ObjectClass
        ObjectGUID = $ObjectGUID
        SamAccountName = $SamAccountName
        SID = $SID
        Owner = $nTSecurityDescriptor.owner
        }
    
    $DistinguishedName = $null
    $DNSHostName = $null
    $Enabled = $null
    $Name = $null
    $ObjectClass = $null
    $ObjectGUID = $null
    $SamAccountName = $null
    $SID = $null
    $nTSecurityDescriptor = $null
    }

# How many Accounts were returns ?
Write-Host $Array.Count -ForegroundColor Yellow

# How many Accounts need to be reviewed ?
$NoGood = $Array.Where({(($_.Owner -ne "$Domain\Domain Admins") -and ($_.Owner -ne "$Domain\Enterprise Admins") -and ($_.Owner -ne "BUILTIN\Administrators"))})
Write-Host $NoGood.count -ForegroundColor Cyan



Script - SCCM - Endpoint Protection Info

Le script SQL ci-dessous propose de lister les détails de l'état et la configuration des antivirus Defender pour des machines gérés par SCCM.

 

/****** ALL WORKSTATIONS ENDPOINT PROTECTION DETAILS  ******/

	SELECT DISTINCT (S.ResourceID)
	,S.Name0 AS 'Machine Name'
	,AD_Site_Name0 AS 'AD Site'
	,S.Operating_System_Name_and0 AS 'Operating System'
	,CASE [EpProtected]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Protected'
	,CASE [EpAtRisk]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Computer at Risk'
	,CASE [EpNotYetInstalled]
		WHEN 1 THEN 'NOT INSTALLED'
		WHEN 0 THEN 'INSTALLED'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Installed'
	,CASE [EpUnsupported]
		WHEN 1 THEN 'UNSUPPORTED'
		WHEN 0 THEN 'SUPPORTED'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Support'
	,CASE[Inactive]
		WHEN 1 THEN 'INACTIVE'
		WHEN 0 THEN 'ACTIVE'
		ELSE 'UNKNOWN' 
		END AS 'SCCM Client Activity'
	,CASE[NotClient]
		WHEN 1 THEN 'NO'
		WHEN 0 THEN 'YES'
		ELSE 'UNKNOWN' 
		END AS 'SCCM Client'
	,CASE [AmRemediationFailed]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Failed Remediation'
	,CASE [AmFullscanRequired]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Full Scan Required'
	,CASE [AmRestartRequired]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Restart Required'
	,CASE [AmOfflineScanRequired]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Offline Scan Required'
	,CASE [AmManualStepsRequired]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Manual Scan Required'
	,CASE [AmRecentlyCleaned]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Recently Cleaned'
	,CASE [AmThreatActivity]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Threat Activity'
	,CASE [EpInstallFailed]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Failed Install'
	,CASE [EpEnforcementSucceeded]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Enforce Succeed'
	,CASE [EpEnforcementFailed]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Enforce Failed'
	,CASE [EpPendingReboot]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Pending Reboot'
	,CASE [Unhealthy]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Unhealthy'
	,CASE [SignatureUpTo1DayOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Signature Age 1 day old'
	,CASE [SignatureUpTo3DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Signature Age 3 day old'
	,CASE [SignatureUpTo7DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Signature Age 7 day old'
	,CASE [SignatureOlderThan7Days]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Signature Age over 7 day old'
	,CASE [NoSignature]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'No Signature'
	,CASE [AmPending]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware Pending'
	,CASE [LastScanUpTo2DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Last Scan 2 days old'
	,CASE [LastScanUpTo8DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Last Scan 8 days old'
	,CASE [LastScanUpTo31DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Last Scan 31 days old'
	,CASE [LastScanOver31DaysOld]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Last Scan Over 31 days old'
	,CASE [Healthy]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Healthy'
	,CASE [Active]
		WHEN 1 THEN 'ACTIVE'
		WHEN 0 THEN 'INACTIVE'
		ELSE 'UNKNOWN' 
		END AS 'Client Activity'
	,CASE [EpUnmanaged]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Not Managed'
	,CASE [EpToBeInstalled]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint To be installed'
	,CASE [EpManaged]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Managed'
	,CASE [EpInstalled]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Managed'
	,CASE [EpEnforced]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Enforced'
	,CASE [EpEnabled]
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Endpoint Enabled'
	
	,CASE AMSH.Enabled 
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'AntiMalware_Enabled'
	,AMSH.Version as AntiMalware_Version
	--,AMSH.ProductStatus
	,CASE AMSH.RtpEnabled
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'RealTime_Ptct_Enabled'
	,CASE AMSH.OnAccessProtectionEnabled -- Specifies whether the computer is monitoring file and program activity on your computer
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'OnAccess_Ptct_Enabled'
	,CASE AMSH.IoavProtectionEnabled -- Scan all downloaded files and attachments
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Downloaded_Ptct_Enabled'
	,CASE AMSH.BehaviorMonitorEnabled
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Behavior_Monitor_Enabled'
	,CASE AMSH.AntivirusEnabled
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Antivirus_Enabled'
	,CASE AMSH.AntispywareEnabled
		WHEN 1 THEN 'YES'
		WHEN 0 THEN 'NO'
		ELSE 'UNKNOWN' 
		END AS 'Antispyware_Enabled'
	,AMSH.EngineVersion
	,AMSH.LastQuickScanDateTimeStart as 'Last QuickScan DateTime Start'
	,AMSH.LastQuickScanDateTimeEnd as 'Last QuickScan DateTime End'
	,AMSH.LastFullScanDateTimeStart as 'Last FullScan DateTime Start'
	,AMSH.LastFullScanDateTimeEnd as 'Last FullScan DateTime End'
	,AMSH.LastFullScanAge as 'Last FullScan Age'
	,AMSH.LastQuickScanAge as 'Last Quick Scan Age'
	,AMSH.AntivirusSignatureUpdateDateTime as 'Antivirus Signature Update DateTime'
	,AMSH.AntiSpywareSignatureUpdateDateTime as 'AntiSpyware Signature Update DateTime'
	,AMSH.AntivirusSignatureAge as 'Antivirus Signature Age'
	,AMSH.AntispywareSignatureAge as 'Antispyware Signature Age'
	,AMSH.AntivirusSignatureVersion as 'Antivirus Signature Version'
	,AMSH.AntispywareSignatureVersion as 'Anti spyware Signature Version'
	
	FROM [CM_BIM].[dbo].[v_EndpointProtectionStatus] EPPS /*(v_EndpointProtectionStatus: Fournit un résumé de l'état des clients Endpoint Protection global pour chaque ordinateur)*/
	INNER JOIN v_R_System S on S.ResourceID = EPPS.ResourceID
	INNER JOIN v_GS_AntimalwareHealthStatus AMSH on AMSH.ResourceID = EPPS.ResourceID /*(v_GS_AntimalwareHealthStatus:  Most recent snapshot of the AntimalwareHealthStatus WMI class for each client where EndPoint Protection is installed)*/