PI Services

Le blog des collaborateurs de PI Services

Intune : Contrôle de la version des mises à jour de fonctionnalités (Feature updates)

Dans Intune, les stratégies de déploiement des mises à jour Windows sont découpés en deux principales catégories :

  • Update rings for Windows 10 and later
  • Feature updates for Windows 10 and later

Si vous créez une stratégies de déploiement Update rings for Windows 10 and later et la déployez sur un groupe d'ordinateurs, les appareils Windows 10/11 vont récupérer les dernières mises à jour de qualité mais aussi de fonctionnalités (Feature updates).

Exemple : un ordinateur sous Windows 10 20H2 passera à 21H2

Dans un scénario où on souhaite bloquer les mises à jour de fonctionnalités Windows 10/11 sur une version bien définie, il faut cibler les ordinateurs concernés par une stratégie Feature updates for Windows 10 and later.

Exemple : dans les paramètres de la stratégie, on définie la version 20H2 --> les ordinateurs seront bloqués sur cette version jusqu'à ce que l'administrateur choisisse de les mettre à jour vers une version ultérieure de Windows.

Tant que la version de fonctionnalités reste statique, les ordinateurs peuvent continuer à installer les mises à jour de qualité et de sécurité disponibles pour leur version de fonctionnalité.

Windows Server - Collecter des logs de crash applicatifs avec Windows Error Reporting

Windows Error reporting aussi abrégé WER a été introduit depuis Windows Vista et permet de générer un fichier de dump lorsqu'un processus crash. C'est particulièrement utile car tous les processus ne génèrent pas forcément leur propre logs.

 

Identifier le processus fautif

L'identification du processus qui crash se fait à l'aide du gestionnaire d'événement ou event viewer en anglais. Il permet d'accéder à plusieurs informations du crash tel que l'heure et le nom du processus en erreur.

Dans l'exemple suivant, on voit que c'est une défaillance du process lsass.exe qui fait planter la machine et le module responsable du crash de lsass.exe est ntdll.dll

 

Activer la génération de crash dump pour le processus en erreur

  1. Ouvrir l'éditeur de registre regedit sur le serveur qui est concerné par le crash
  2. Naviguer jusqu'à HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps
  3. Créer une nouvelle Key qui a comme valeur le nom du processus défaillant, dans notre exemple lsass.exe
  4. Dans la précédente clé, créer un premier DWORD (32-bit)
    1. Name : DumpCount
    2. Valeur : le nombre de crash dump a sauvegarder, par exemple 5
  5. Créer un deuxième DWORD (32-bit) value
    1. Name : DumpType
    2. Valeur : 2 cela permet de générer un full crash dump, celui-ci est le plus complet
  6. Créér une String value
    1. Name : DumpFolder
    2. Valeur : le chemin complet du dossier qu'il faudra créer et qui contiendra les dumps, par exemple c:\temp
  7. Créer un dossier temp dans le disque C:

 

Récupérer le fichier de log et l'analyser

Au prochain crash du processus, un fichier de log sera créé dans le dossier choisi dans la base de registre.

Pour pouvoir lire les fichiers de log, il est nécessaire d'installer un outil tel que WinDbg

 

Supprimer la génération de fichiers de dump

Pour supprimer la génération de log, il faut supprimer la clé précédemment créée dans la base de registre, dans notre exemple cela reviendrait à supprimer la clé lsass.exe

 

Pour aller plus loin

Documentation officielle Microsoft sur l'activation de Windows Error Reporting

Powershell : WSUS Report Servers States

Voici un script qui vous permettra de réaliser un état des lieu sur les mises à jour de sécurité nécessaires pour l'ensemble de vos serveurs rattachés à un WSUS.

Attention : Prenez soins de modifier les variables suivantes :

  1. Sur la ligne 12 vous pouvez remplacer le "-30" par un valeur plus élevé si vous souhaitez un delta plus large (le -30 correspondant à M - 1, et ainsi connaitre le nombre de mise à jours manquante à J-30 jours)
  2. Sur la ligne 14 $Alias (doit correspondra à l'alias renseigné dans votre DNS).
  3. Ligne 44 vous pouvez ajouter un -Credential (si vous n'exécutez pas ce script dans un contexte avec les droits nécessaires).

 

Ce dernier est une version pour un WSUS unique en mode autonome ou déconnecté, ne possédant pas de Downstram Server, je publierais une autre version pour ce cas de figure.

#################################
# Step 1 : Variables Definition #
#################################
#region - Variables
$RootFolder = "C:\Temp\WSUS"
$ResultFolder = "$RootFolder\Result"
$LogFolder = "$RootFolder\Logs"
$Archives = "$RootFolder\Archives"
$AllFolders = $RootFolder, $ResultFolder, $LogFolder, $Archives
$LogFile = "$LogFolder\GeneralLog.log"
$Date = Get-Date -Format M
$TargetDate = ((Get-Date).AddDays(-30)).ToString('MM/dd/yyyy')
$Array = @()
$Alias = "wsus"

# Folders creation
foreach ($Folder in $AllFolders) {
    If (!(Test-Path $Folder)) {
        New-Item $Folder -ItemType Directory
    }
}

# Old Files deletion
Get-ChildItem $LogFolder | Remove-Item -Force
Get-ChildItem $ResultFolder | Remove-Item -Force
#endregion - Variables

###############################
# Step 2 : Locate WSUS Server #
###############################
#region - Locate WSUS Server
Write-Output "Trying to locate WSUS server" | Add-Content $LogFile
Try {
    $WSUSServer = Resolve-DnsName $Alias -ErrorAction Stop | select -ExpandProperty Name -Last 1
    Write-Output "Successfully locate WSUS server" | Add-Content $LogFile
}
Catch {
    Write-Output "Failed to locate WSUS server" | Add-Content $LogFile
}

# Wsus Role Check
Write-Output "Verifying WSUS server role installation" | Add-Content $LogFile
Try {
    $WsusRoleInstalled = Invoke-Command -ComputerName $WSUSServer -ScriptBlock {Get-WindowsFeature | Where-Object {($_.InstallState -eq "Installed") -and ($_.Name -like "UpdateServices")}} -ErrorAction Stop | Select-Object -Property Installed
    Write-Output "Successfully Run the query" | Add-Content $LogFile
    If ($WsusRoleInstalled.Installed -ne $true) {
        # End of Script
        Write-Output "WSUS Server not found" | Add-Content $LogFile
        Exit
    }
}
Catch {
    Write-Output "Failed to verify WSUS server role installation" | Add-Content $LogFile
    Exit
}
#endregion - Locate WSUS Server

############################
# Step 3 : WSUS Connection #
############################
#region - WSUS Connection
# Try on default port 8530
Try {
    $WsusPort = "8530"
    [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
    $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$false,$WsusPort)
    $Connection = $Wsus.Name
}
Catch {
    # Try on port 80
    Try {
        $WsusPort = "80"
        [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
        $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$false,$WsusPort)
        $Connection = $Wsus.Name
    }
    Catch {
        # Try on port 8531
        Try {
            $WsusPort = "8531"
            [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
            $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$true,$WsusPort)
            $Connection = $Wsus.Name
        }
        Catch {
            # Try on port 443
            Try {
                $WsusPort = "443"
                [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
                $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$true,$WsusPort)
                $Connection = $Wsus.Name
            }
            Catch {
                # End of Script
                Write-Output "WSUS Server connection failed on all ports" | Add-Content $LogFile
                Exit
            }
        }
    }
}
#endregion - WSUS Connection

###########################
# Step 4 : WSUS Reporting #
###########################
#region - WSUS Reporting
# Variables Definition
$computerscope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updateClassifications = $Wsus.GetUpdateClassifications() | Where-Object {$_.Title -eq "Security Updates"}
$UpdateScope.Classifications.AddRange($updateClassifications)
$Servers = $wsus.GetComputerTargets($computerscope) | Where-Object {($_.ComputerRole -eq "Server")}
$NBServers = $Servers.Count

Write-Output "WSUS Server has $NBServers to manage" | Add-Content $LogFile
$Servers | Foreach {
    $FullDomainName = $_.FullDomainName
    $WorkstationID = $_.Id
    $LastSyncClient = $_.LastSyncTime
    [STRING]$LastSyncResultClient = $_.LastSyncResult
    $LastReportedStatusTime = $_.LastReportedStatusTime
    $IPAddress = ($_.IPAddress).IPAddressToString
    $Id = $_.Id
    $Make = $_.Make
    $Model = $_.Model
    $OSLanguage = ($_.OSInfo).DefaultUILanguage
    $OSArchitecture = $_.OSArchitecture
    $OSFamily = $_.OSFamily
    $OSDescription = $_.OSDescription
    $ComputerTargetGroupIds = $_.ComputerTargetGroupIds
    $SyncsFromDownstreamServer = $_.SyncsFromDownstreamServer
    $UpdateServer = $_.UpdateServer.Name

    # Get AD Information
    Try {
        Write-Output "Trying to retrieve Active Directory Informations for $FullDomainName" | Add-Content $LogFile
        $CurrentServer = Get-ADComputer -Filter {DNSHostName -eq $FullDomainName} -Properties CanonicalName -ErrorAction Stop | Select-Object Enabled,CanonicalName,OperatingSystem,Name
        Write-Output "Successfully retrieve Active Directory Informations for $FullDomainName" | Add-Content $LogFile
    }
    Catch {
        Write-Output "Failed to retrieve Active Directory Informations for $FullDomainName" | Add-Content $LogFile
    }
    # Define state for Begining and End of year, plus a targeted date (here M-30)
    $updatescope.ToCreationDate=[DATETIME]"01/31/2022"
    $KBToInstallJanuary = $wsus.GetSummariesPerComputerTarget($updatescope,$computerscope) | where {($_.ComputerTargetId -eq $WorkstationID)} |Select-Object -ExpandProperty NotInstalledCount
    $updatescope.ToCreationDate=[DATETIME]"12/31/9999"
    $KBToInstallUpToDate = $wsus.GetSummariesPerComputerTarget($updatescope,$computerscope) | where {($_.ComputerTargetId -eq $WorkstationID)} |Select-Object -ExpandProperty NotInstalledCount
    $updatescope.ToCreationDate=[DATETIME]"$TargetDate"
    $KBToInstallJ30 = $wsus.GetSummariesPerComputerTarget($updatescope,$computerscope) | where {($_.ComputerTargetId -eq $WorkstationID)} |Select-Object -ExpandProperty NotInstalledCount
    $IPAddress = ($_."IPAddress").IPAddressToString
    
    $Array += New-Object psobject -Property @{
        FullDomainName = $FullDomainName
        WorkstationID = $Id
        LastSyncClient = $LastSyncClient
        LastSyncResultClient = $LastSyncResultClient
        LastReportedStatusTime = $LastReportedStatusTime
        IPAddress = $IPAddress
        Enabled = $CurrentServer.Enabled
        CanonicalName =$CurrentServer.CanonicalName
        Name = $CurrentServer.Name
        KBNeededInJanuary = $KBToInstallJanuary
        KBNeededLastMonth = $KBToInstallJ30
        KBNeededToBeUpToDate = $KBToInstallUpToDate
        Id = $Id
        Make = $Make
        Model = $Model
        OSLanguage = $OSLanguage
        OSArchitecture = $OSArchitecture
        OSFamily = $OSFamily
        OSDescription = $OSDescription
        ComputerTargetGroupIds = $ComputerTargetGroupIds
        SyncsFromDownstreamServer = $SyncsFromDownstreamServer
        UpdateServer = $UpdateServer
    }
    # Release
    $FullDomainName = $null
    $WorkstationID = $null
    $LastSyncClient = $null
    $LastSyncResultClient = $null
    $LastReportedStatusTime = $null
    $IPAddress = $null
    $Id = $null
    $Make = $null
    $Model = $null
    $OSLanguage = $null
    $OSArchitecture = $null
    $OSFamily = $null
    $OSDescription = $null
    $ComputerTargetGroupIds = $null
    $SyncsFromDownstreamServer = $null
    $CurrentServer = $null
    $UpdateServer = $null
    $KBToInstallJanuary = $null
    $KBToInstallUpToDate = $null
    $KBToInstallJ30 = $null
}
#endregion - WSUS Reporting

########################
# Step 4 : Export Data #
########################
#region - Export Data
$Array | select WorkstationID,Make,Model,OSFamily,OSDescription,OSArchitecture,OSLanguage,IPAddress,FullDomainName,Name,CanonicalName,Enabled,SyncsFromDownstreamServer,UpdateServer,LastReportedStatusTime,LastSyncClient,LastSyncResultClient,KBNeededInJanuary,KBNeededLastMonth,KBNeededToBeUpToDate | Export-Csv $ResultFolder\Report.csv -Delimiter ";" -Encoding UTF8 -NoTypeInformation

#endregion - Export Data

 

 

[AD Connect] - Event Id 528

Contexte

Après un reboot du serveur AD Connect, le service "ADSync" reste en "Starting" ("démarrage" pour les OS en FR), et après quelque temps une Alerte Azure devrait remonter avec le message suivant :

En allant jeter un oeil dans les journaux d'événements, dans le journal "Application" vous devriez trouver l'erreur suivante :

Event Id 528

"Windows API call WaitForMultipleObjects returned error code: 575. Windows system error message is: {Application Error}

The application was unable to start correctly (0x%lx). Click OK to close the application.

Reported at line: 3714."

 

Bon a ce stade je vais être honnête, ça sent pas bon, la base de données locale d'AD Connect est dans un mauvais état (les fichiers sont corrompus) et nous devrions normalement réinstaller le serveur mais, avant cela on va d'abord essayer un petit "tricks".

 

Solution

1-Arrêter le service "ADSync"

Nous allons dans un premier temps "Désactiver" le service "ADSync" et redémarrer le serveur.

Ouvrez la console des services "Services.msc", sélectionnez le service "ADSync", faites un clic droit "Properties" et changez le "Startup Type" en sélectionnant "Disabled".

 

Enfin redémarrez le serveur.

2-Remplacer les fichiers corrompus

Après le redémarrage du serveur, connectez vous et ouvrez un explorateur jusqu'ici (Attention : Remplacez "XXXXXXXXXXXXX" par le nom du compte que vous utilisez) :

C:\Users\XXXXXXXXXXXXX\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\ADSync2019” (C'est ici que se trouve les  fichiers corrompus).

Ouvrez un second explorateur jusqu'ici :

C:\Program Files\Microsoft SQL Server\150\LocalDB\Binn\Templates

Dans ce second explorateur copiez les fichiers suivants

  • Model.mdf
  • Modellog.ldf

Enfin collez les dans le premier explorateur (“C:\Users\XXXXXXXXXXXXX\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\ADSync2019”) :

 

Confirmez le remplacement des fichiers

 

3-Remmettre le service ADSync en état.

Ouvrez la console des services "Services.msc", sélectionnez le service "ADSync", faites un clic droit "Properties" et changez le "Startup Type" en sélectionnant "Automatic".

4-Redémarrer le service.

Démarrez le service, ouvrez la console des services "Services.msc", sélectionnez le service "ADSync", faites un clic droit et sélectionnez "Start"

 

Si vous êtes dans un jour de chance le service démarre et la synchronisation commence.

 

Conclusion

En conclusion, nous ne parvenons pas à expliquer ce qui a causé la corruption des fichiers lors du dernier redémarrage mais avec ce petit "tricks" on a pu éviter la réinstallation du serveur AD Connect.

Powershell : Lister les membres externes des OneDrives

Attention au côté juridique de cette opération. 

Dans certain cas de figure nous sommes amenés à devoir faire une liste de tout accès externe à différents types de ressources, dans le cas présent nous allons lister l'ensemble des OneDrives avec un partage externe, pour ce faire vous aurez besoin de :

  • Powershell 5.1,
  • Le module Sharepoint Online,
  • un accès Sharepoint Admin.

Vous pouvez maintenant lancer le script en prenant soin de modifier les url de connexion (L2 et L59) 

# Connection to Sharepoint Online
Connect-SPOService -Url https://contoso-admin.sharepoint.com # (Please change the connection URL)

# Get the list of All OneDrives
$AllOneDrive = Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Url -like '-my.sharepoint.com/personal/'"

# Define variables
$ArrayOneDriveMembers = @()
$SortOneDrive = $AllOneDrive | sort Url
$X = 0

$SortOneDrive | foreach {
    $OneDriveUrl = $_.Url
    $OneDriveOwner = $_.Owner
    $OneDriveStorageQuota = $_."Storage Quota"
    $OneDriveTitle = $_.Title
    # Get Member of current OneDrive
    Try {
        $OneDriveMembers = Get-SPOUser -Site $OneDriveUrl -ErrorAction stop
        }
    Catch {
        Write-Warning $($_)
        Write-Output "$OneDriveUrl;$OneDriveTitle" | Add-Content C:\temp\Onedriveerrors.txt
        }
    
    # Get External members
    $OneDriveExternal = $OneDriveMembers.where({$_.Usertype -eq "Guest"})
    If ($OneDriveExternal.count -ne 0) {
        Write-Host "External Users are present in $OneDriveTitle" -ForegroundColor Green
        $OneDriveExternal.count
        $OneDriveExternal | foreach {
            $DisplayName = $_.DisplayName
            $LoginName = $_.LoginName

            # Store Data
            $ArrayOneDriveMembers += New-Object psobject -Property @{
                OneDriveUrl = $OneDriveUrl
                OneDriveOwner = $OneDriveOwner
                OneDriveStorageQuota = $OneDriveStorageQuota
                OneDriveTitle = $OneDriveTitle
                DisplayName = $DisplayName
                LoginName = $LoginName
                }
            # Release
            $DisplayName = $null
            $LoginName = $null
            }
        }
    # Release
    $OneDriveUrl = $null
    $OneDriveOwner = $null
    $OneDriveStorageQuota = $null
    $OneDriveTitle = $null
    $OneDriveMembers = $null
    
    # After 500 connection, reconnect to Sharepoint
    If ($x -eq 500) {
        # Connect to Sharepoint
        Connect-SPOService -Url https://contoso-admin.sharepoint.com 
        $x =0
        }
    $x++
    }

# Export Data
$ArrayOneDriveMembers | Export-Csv c:\temp\ExternalOneDriveMembers.csv -Encoding UTF8 -Delimiter ";" -NoTypeInformation

 

Intune : Trouver le dernier utilisateur connecté à un ordinateur en utilisant Microsoft Graph explorer

Dans la console d'administration Intune, Microsoft n'a pas ajouté un rapport ou une propriété ordinateur pour identifier le dernier utilisateur connecté à un device (last logged on user).

Si vous souhaiter identifier quel est le dernier utilisateur connecté à un ordinateur, vous pouvez utiliser l'interface graphique Microsoft Graph explorer: https://developer.microsoft.com/en-us/graph/graph-explorer

Dans Microsoft Graph explorer, précisez dans le Request body, l'url de l'ordinateur recherché: https://graph.microsoft.com/beta/deviceManagement/managedDevices/{<Device ID>}  (remplacez <Device ID> par la vraie valeur qui peut être récupérée depuis Azure AD dans les propriétés d'un ordinateur).

Nous avons récupéré le "Device ID" de l'ordinateur concerné depuis Azure AD et l'avons remplacé dans l'url comme le montre la capture d'écran ci-dessous, puis nous avons cliqué sur le bouton Run query :

Dans la même capture d'écran, nous pouvons voir dans la partie Response preview (Résultat), l'ID du dernier utilisateur connecté à l'ordinateur.

Avec cet ID, nous pouvons chercher l'utilisateur dans Azure AD.

Script - Crowdstrike - Exemple d'utilisation du module PSFalcon

Falcon Crowdsrike est l'un des antivirus next generation (EDR) les plus en vue et les plus performants du marché.  Le script ci-dessous propose un exemple de requetage de l'API de Crowdstrike via l'utilisation du module Powershell PSFalcon (https://www.powershellgallery.com/packages/PSFalcon)

L'acces et l'utilisation de l'Api Crowdstrike requiert bien sur un abonnement Crowdstrike (https://www.crowdstrike.com/)

Le script utilisant pour l'acces a l'API, une clé d'encryptage (AES.key), un fichier contenant le SecID (SecID.txt), et un fichier contenant la passphrase (pass.txt), il est necessaire de recreer ces éléments avec le contexte de l'abonnement auquel on se connecte (SecID).

Le script interroge l'API pour recuperer des éléments tel que les hotes, les comportements (Behaviors), les detections, les incidents.

 

QueryCrowdstrike.ps1 (11,62 kb)

 

##############################################
### QUERY CROWDSTRIKE WITH PSFALCON MODULE ###
##############################################


<# 

    .SYNOPSIS 
        INTERROGATION D'UN TENANT FALCON CROWDSTRIKE VIA L'UTILISATION DU MODULE POWERSHELL PSFALCON
        NB: LES DONNEES SONT EXTRAITES SOUS FORME D'UN FICHIERS CSV 

    .PARAMETER  
        ClientID : Client ID crowdstrike
        Pass : Password du compte d'acces a l'API
        
        ExportFolder : Dossier d'export du fichier CSV
        LogFolder : Chemin du dossier où creer le log du script

 
    .EXAMPLE 
     .\QueryCrowdstrike.ps1 -CloudUrl "https://api.eu-1.crowdstrike.com"  -ClientID <clientid> -Pass <pass> -ExportFolder ./ -LogFolder ./
#>


[CmdletBinding()]
param(
[Parameter(Mandatory,HelpMessage="Cloud URL")]
$CloudUrl,

[Parameter(HelpMessage="Client ID crowdstrike")]
$ClientID,

[Parameter(HelpMessage="Password du compte d'acces a l'API")]
$Pass,

[Parameter(Mandatory,HelpMessage="Dossier d'export du fichier CSV")]
[string]$ExportFolder="./CSV",

[Parameter(Mandatory,HelpMessage="Chemin du dossier où creer le log du script")] 
[string]$LogFolder="./",

[Parameter(Mandatory,HelpMessage="Chemin de la clé utilisée pour l'encryption du CID et de la passphrase")] 
$KeyFile = "D:\MyFolder\CROWDSTRIKE\AES.key",

[Parameter(Mandatory,HelpMessage="Chemin du fichier contenant le SecID du client")] 
$SecIDFile = "D:\MyFolder\CROWDSTRIKE\SecID.txt",

[Parameter(Mandatory,HelpMessage="Chemin du fichier contenant la passphrase")] 
$PassFile = "D:\MyFolder\CROWDSTRIKE\Pass.txt"


)


# TO RENEW KEY FILE USED FOR CLIENT ID AND PASSPHRASE ENCRYPTION
<#
    # Generate Key File
    $Key = New-Object Byte[] 16   # You can use 16, 24, or 32 for AES
    [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
    $Key | out-file $KeyFile

#>


# TO RENEW SECID ENCRYPTION, EXECUTE THE CODE BETWEEN <# #>

<#
    # Create secure string object
    $SecIDFile = "D:\MyFolder\CROWDSTRIKE\SecID.txt"
    $Key = Get-Content $KeyFile
    $SecID = read-host  -Prompt "Enter client SecID" -AsSecureString
    $SecID | ConvertFrom-SecureString -key $Key | Out-File $SecIDFile

#>


# TO RENEW PASSPHRASE ENCRYPTION, EXECUTE THE CODE BETWEEN <# #>

<#
    # Create secure string object
    $PassFile = "D:\MyFolder\CROWDSTRIKE\Pass.txt"
    $Key = Get-Content $KeyFile
    $Pass = read-host  -Prompt "Enter client PassPhrase" -AsSecureString
    $Pass | ConvertFrom-SecureString -key $Key | Out-File $PassFile

#>






# OTHER VARIABLES
# Script Name
$ScriptName = "QueryCrowdstrike.ps1"
# LogName = ScriptName without extension
$Log = $ScriptName.Split('.')[0]


# FUNCTIONS


function Write-Log 
{ 
    <# 
    .SYNOPSIS 
        This function creates or appends a line to a log file. 
 
    .PARAMETER  Message 
        The message parameter is the log message you'd like to record to the log file. 
 
    .EXAMPLE 
        PS C:\> Write-Log -Message 'Value1' 
        This example shows how to call the Write-Log function with named parameters. 
    #> 
    [CmdletBinding()] 
    param ( 
        [Parameter(Mandatory)] 
        [string]$Message,
        [Parameter(Mandatory)] 
        [string]$LogPath, 
        [Parameter(Mandatory)] 
        [string]$LogName
        
    ) 
     
    try 
    { 
        $DateTime = Get-Date -Format ‘MM-dd-yy HH:mm:ss’ 
        Add-Content -Value "$DateTime - $Message" -Path "$LogPath\$LogName.log" 
    } 
    catch 
    { 
        Write-Error $_.Exception.Message 
    } 
} 



            




# Requis si l'import du module echoue en en renvoyant plusieurs messages 'Unable to find [System.Net.Http.(...)*]' 
Add-Type -AssemblyName System.Net.Http 

# Import Module
Import-Module -Name D:\MyFolder\CROWDSTRIKE\Powershell_Module\psfalcon-master\PSFalcon.psm1



$Key = Get-Content $KeyFile

# Get file content and Decrypt ClientID
$ClientID = Get-Content $SecIDFile | ConvertTo-SecureString -Key $key
$ClientID = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ClientID))

# Get file content and Decrypt Pass
$Pass = Get-Content $PassFile | ConvertTo-SecureString -Key $key
$Pass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pass))







# Query for Falcon Token
try
    {
    Request-FalconToken -ClientId $ClientID -ClientSecret $Pass -Hostname $CloudUrl
    }
catch
    {
    $Message = "Error during Request-FalconToken - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


# Test if Test-FalconToken return True
if ($(Test-FalconToken).token -eq $true)  
      {
      $Message = "Token is valid - OK"
      write-host -F Green $Message
      Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
      }
Else
    {
    $Message = "Problem with Token - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }




# GET DATA

#region Falcon Hosts 

# Get Falcon Hosts (exemple option -filter:  –Filter "hostname:'SRV'")
try
    {
    $FalconHosts = Get-FalconHost -All -Detailed
    #$FalconHosts | select hostname,os_version,agent_version,status | ft -AutoSize
    }
catch
    {
    $Message = "Problem during Get-FalconHost - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


# Convert To CSV
 try
    {
    $FalconHostsCSV = $($FalconHosts | select `
        hostname,
        os_version,
        platform_name,
        status,
        agent_version,
        config_id_base,
        config_id_build,
        @{Name='First_Seen';Expression={[datetime]::ParseExact($_.first_seen.Replace('T','').Replace('Z',''),'yyyy-MM-ddHH:mm:ss',$null)}},
        @{Name='Last_Seen';Expression={[datetime]::ParseExact($_.last_seen.Replace('T','').Replace('Z',''),'yyyy-MM-ddHH:mm:ss',$null)}},
        reduced_functionality_mode | ConvertTo-Csv -Delimiter ',' -NoTypeInformation).Replace('"','')
    
    # Display CSV File
    $FalconHostsCSV
    }
catch
    {
    $Message = "Error during convert to CSV file for FalconHosts - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }

# Export CSV
 try
    {   
    $FalconHostsCSV | Out-File -FilePath "$ExportFolder`/FalconHosts.csv" -Force
    }
catch
    {
    $Message = "Error during export of CSV file for FalconHosts - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


#endregion Falcon Hosts 







#region Falcon Detection




#Get-FalconDetection
write-host "FALCON DETECTION EVENTS" -B White -f Blue
try
    {
    $FalconDetection = Get-FalconDetection -Detailed -All  
    $FalconDetection | sort created_timestamp -Descending
    }
catch
    {
    $Message = "Error during Get-FalconDetection - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


# Convert To CSV
 try
    {
    $FalconDetectionCSV = $($FalconDetection | select `
    detection_id,
    @{Name='Detection_Creation_Date';Expression={[datetime]::ParseExact($_[0].created_timestamp.split('.')[0].Replace('T',''),'yyyy-MM-ddHH:mm:ss',$null)}},
    @{Name='HostName';Expression={$_.device.hostname}},
    @{Name='HostIP';Expression={$_.device.local_ip}},
    @{Name='DeviceID';Expression={$_.device.device_id}},
    @{Name='BehaviorID';Expression={$_.Behaviors.Behavior_id}},
    @{Name='Behavior_FileName';Expression={$_.Behaviors.filename}},
    @{Name='Behavior_FilePath';Expression={$_.Behaviors.filepath}},
    @{Name='Behavior_cmdline';Expression={$_.Behaviors.cmdline}},
    status,
    max_severity_displayname,
    email_sent | ConvertTo-Csv -Delimiter ',' -NoTypeInformation).Replace('"','')
    }
catch
    {
    $Message = "Error during convert to CSV file for FalconDetection - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }

# Export CSV
 try
    {   
    $FalconDetectionCSV | Out-File -FilePath "$ExportFolder`/FalconDetection.csv"
    }
catch
    {
    $Message = "Error during export of CSV file for FalconDetection - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }




#endregion Falcon Detection




#region Falcon Behavior

# Get-FalconBehavior (NB: Transform TimeStamp to more readable format with [datetime]::ParseExact)
write-host "FALCON BEHAVIOR EVENTS" -B White -f Blue

try
    {
    $FalconBehavior = Get-FalconBehavior | foreach {Get-FalconBehavior -Ids $_} | Select -property  behavior_id,display_name,objective,@{Name='BehaviorDate';Expression={[datetime]::ParseExact($_.timestamp.Replace('T','').replace('Z',''),'yyyy-MM-ddHH:mm:ss',$null)}},cmdline,filepath,user_name,incident_id,tactic_id,tactic,technique_id,technique | sort BehaviorDate -Descending
    $FalconBehavior
    }
catch
    {
    $Message = "Error during Get-FalconBehavior - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


# Convert To CSV
 try
    {
    $FalconBehaviorCSV = $($FalconBehavior | select BehaviorDate,cmdline,filepath,user_name | ConvertTo-Csv -Delimiter ';' -NoTypeInformation).Replace('"','')
    }
catch
    {
    $Message = "Error during convert to CSV file for FalconBehavior - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }

# Export CSV
 try
    {   
    $FalconBehaviorCSV | Out-File -FilePath "$ExportFolder`/FalconBehavior.csv"
    }
catch
    {
    $Message = "Error during export of CSV file for FalconBehavior - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }



#endregion Falcon Behavior





#region Falcon Incident

write-host "FALCON INCIDENTS EVENTS" -B White -f Blue
try
    {
    $FalconIncidents= Get-FalconIncident -Detailed -All  
    $FalconIncidents | sort created -Descending
    }
catch
    {
    $Message = "Error during Get-FalconIncident - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }


# Convert To CSV
 try
    {
    $FalconIncidentsCSV = $($FalconIncidents | select `
    incident_id,
    @{Name='Creation_Date';Expression={[datetime]::ParseExact($_.created.Replace('T','').Replace('Z',''),'yyyy-MM-ddHH:mm:ss',$null)}},
    state,
    email_state  | ConvertTo-Csv -Delimiter ',' -NoTypeInformation).Replace('"','')



        

    }
catch
    {
    $Message = "Error during convert to CSV file for FalconIncident - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }

# Export CSV
 try
    {   
    $FalconIncidentsCSV | Out-File -FilePath "$ExportFolder`/FalconIncidents.csv"
    }
catch
    {
    $Message = "Error during export of CSV file for FalconIncidents - END OF SCRIPT"
    write-host -F Red $Message
    Write-Log -Message $Message -LogPath $LogFolder -LogName $Log
    EXIT 1
    }




#endregion Falcon Incident

 

Active Directory - Comment exporter la chaîne de certification d'un contrôleur de domaine ?

Certaines applications pour pouvoir voir communiquer à travers des protocoles SSL sécurisés telles que LDAPS (port 636) et GC over SSL (3269) doivent avoir l'ensemble de la chaîne de certification d'un certificat d'un contrôleur de domaine (DC). L'export des certificats de la chaîne de certification ainsi que la constitution du certificat finale peut se faire à travers les outils natifs de Windows Server.

Exporter un certificat depuis la console de certification

L'ensemble des manipulations décrites peuvent se faire à distance depuis la console de certificat, mais dans un souci de clarté, les actions décrites sont réalisés directement depuis un contrôleur de domaine.

Depuis une invite de commande, entrer la commande suivante

certlm #cmdlet qui permet d'ouvrir la console de certificats avec le contexte de l'ordinateur local, donc ici le contrôleur de domaine

 

Déployer le dossier Personal puis Certificates

 

Identifier le certificat qui sert à l'authentification des clients du contrôleur de domaine :

  • La colonne Issued To contient habituellement : le FQDN du contrôleur de domaine
  • La colonne Intended Purposes contient habituellement : Client Authentication, Server Authentication
  • La colonne Certificate Template contient habituellement : Domain Controller

 

Faire un click droit sur le certificat > All Tasks > Export...

 

L'utilitaire d'exportation de certificat se lance, choisir de ne pas exporter la clé privée : No, do not export the private key

 

Choisir un format Base-64 encoded X.509 (.CER)

 

Choisir un dossier de destination dans lequel exporter le certificat



Finir l'export du certificat via l'utilitaire

Le certificat du contrôleur de domaine a été exporté, il faut désormais exporter les autres certificats de la chaîne de certification : le certificat racine et les certificats intermédiaires 

 

Exporter chaque certificat de la chaîne de certification

De retour dans la console certlm, faire un click droit sur le certificat > All Tasks > Export...


 

Ouvrir l'onglet Certification Path, l'ensemble des certificats de la chaîne de certification est listé, le premier de la liste étant le certificat racine, le dernier le certificat du contrôleur de domaine, et les certificats entre les deux sont les certificats intermédiaires.

Cliquer sur le certificat racine puis sur View Certificate, le certificat racine s'affiche alors dans son propre onglet, cliquer sur l'onglet Details puis Copy to File...

Le même utilitaire d'export de certificat s'affiche, compléter l'exporter du certificat racine avec les mêmes options que pour le certificat du contrôleur de domaine en donnant un nom explicite au certificat pour pouvoir le différencier.

Exporter de la même façon l'ensemble des certificats intermédiaires.

 

Assembler les certificats de la chaîne de certification

Une fois l'ensemble des certificats de la chaîne de certification exportés, il faut les assembler dans un seul certificat.

  • Créer un fichier texte et remplacer l'extension .txt par .cer, dans cet exemple, on l'appellera DCCertificateFullChain.cer 
  • Ouvrir le certificat racine avec un éditeur de texte comme Notepad et copier coller le contenu dans le certificat jusqu'alors vide DCCertificateFullChain.cer
  • Dans le fichier DCCertificateFullChain.cer faire un saut de ligne après ----END CERTIFICATE----
  • Ouvrir chaque certificat intemédiaire et les copier coller à la suite du certificat racine dans le fichier DCCertificateFullChain.cer en respectant le saut de ligne
  • Finir par le certificat du contrôleur de domaine

Les certificats doivent se suivre comme suit :

 

Le certificat avec l'ensemble de la chaîne de certification est prêt.

 

Activation Windows 10 : Passer de l'activation KMS à la licence numérique (abonnement)

Si vous déplacez tout vers Microsoft 365 et Azure AD, l'un des problèmes qui peuvent être rencontrés, c'est quand vous supprimez un ordinateur du domaine OnPrem et vous faites sa jonction à l'Azure AD, Windows est toujours activé à l'aide de l'hôte KMS.

Afin de passer à l'activation step-up (licence numérique), il faut installer la clé de produit Firmware-embedded (OEM), puis elle va automatiquement changer à la licence numérique.

Pour remplacer la clé de produit par la clé Firmware-embedded, tapez dans l'invite de commande élevée :

wmic path SoftwareLicensingService obtenir OA3xOriginalProductKey

slmgr -ipk <clé de produit de la dernière étape>

Un redémarrage peut être nécessaire et l'ouverture de session avec un utilisateur disposant d'un abonnement E3/E5 valide.

Après cela, l'activation devrait indiquer "L'abonnement Windows 10 Enterprise est actif" et "Windows est activé à l'aide d'une licence numérique".

Note:

Il semble que pour utiliser l'activation step-up, Windows 10 a besoin d'une clé d'activation/produit valide avant de pouvoir effectuer la mise à niveau.

 

Power BI Desktop - Exemple d'extraction et utilisation d'une date issue d'un champ texte

Problématique :  Un champ texte apparenté au contenu d’un log, melange de texte et de chiffre, contiens une date que l’on veut récupérer.

 

  • On veut récupérer la date de création (Creation Date) et la mettre dans une nouvelle colonne
  • On remarque que la date est situé entre deux chaines de delimitation : ‘Creation Date : "’ et ‘"’

De ce fait on selectionne la colonne et dans le menu ‘Add Column’ on clique sur Extract/Text Between Delimiters

 

Dans le champ ‘Start delimiter’ on renseigne notre premiere chaine (ici Creation Date : ") (NB : attention aux espaces)

Dans le champ ‘End delimiter’ on renseigne notre deuxième chaine (ici ")

On valide.

Une colonne est crée automatiquement au format chaine (ABC) a la fin du tableau avec le nom ‘Text Between Delimiters’

On fait un clic droit sur la colonne et on selectionne Change Type\Date/Time/Timezone

Attention. Dans cet exemple la conversion fonctionne car le système detecte que le pattern de la chaine est de type Date/Time/Timezone.  Dans d’autres cas il peut être nécessaire de tester d’autres format et/ou bien de sélectionner a l’origine, une portion differente de texte dans la colonne d’origine.

La conversion est OK :

Il ne reste plus qu’a renommer la colonne de manière plus explicite. (Ex : Custo_Creation_Date)