PI Services

Le blog des collaborateurs de PI Services

Script - SCCM - Recuperation et export des Distribution points

Le script ci-dessous requete la base SQL de SCCM pour lister et exporter en CSV, les points de distribution SCCM

 

########################################################################################################
### REQUETE LA BASE SQL DE SCCM POUR OBTENIR LA LISTE DES POINTS DE DISTRIBUTION SCCM.
### EXPORT DES RESULTAT EN FICHIER CSV  #####
######################################################################################################## 

# AUTHOR: CJOURDAN

<# 

    .SYNOPSIS 
        REQUETE LA BASE SQL DE SCCM POUR OBTENIR LA LISTE DES POINTS DE DISTRIBUTION SCCM
        EXPORT DU RESULTAT EN FICHIER CSV.

    .PARAMETER  
        SQLInstance : Instance SQL
        SQLDB : Instance SQL
        SQLQuery : Requete SQL
        ExportFolder : Dossier d'export du fichier CSV
        LogFolder : Chemin du dossier où creer le log du script

 
    .EXAMPLE 
     .\SCCM_SCCM_Distribution_Points.ps1 -SQLInstance SQLSCCM\SCCM -SQLDB CM_BIM -ExportFolder C:\MyExport -LogFolder C:\MyLogs
#>


[CmdletBinding()]
param(
[Parameter(Mandatory=$true,HelpMessage="Instance SQL")]
[string]$SQLInstance,

[Parameter(Mandatory=$true,HelpMessage="Base SQL")]
[string]$SQLDB,

[Parameter(Mandatory=$false,HelpMessage="Requete SQL")] 
[string]$SQLQuery= $("/* --- ALL SCCM DISTRIBUTION POINTS --- */

Declare @UserSIDs As Varchar(25); 
Set @UserSIDs = 'Disabled'

SELECT DISTINCT                 
        dp.ServerName AS Distribution_Point 
        
from fn_rbac_SystemResourceList(@UserSIDs)  as sys 
join fn_rbac_DistributionPointInfo(@UserSIDs)  as dp 
on sys.NALPath = dp.NALPath 
where sys.RoleName = 'SMS Distribution Point'

"),


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

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

)


# SCRIPT NAME
$ScriptName = "SCCM_SCCM_Distribution_Points.ps1"


# LogName = ScriptName without extension
$Log = $ScriptName.Split('.')[0]


### FUNCTIONS

# Function Write-Log



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 
    } 
} 


Function GetSQLData {

<# 
    .SYNOPSIS 
        This function query SQL Database and get Data 
 
    .PARAMETER  
        SQLInstance: Instance SQL.
        SQLDB: Base SQL.
        SQLQuery: Requete SQL.

 
    .EXAMPLE 
        GetSQLData -SQLInstance "MyInstance" -SQLDB "MyDB" -SQLQuery "Select * from MyView"
    #> 


[CmdletBinding()] 
    Param( 
        [Parameter(Mandatory=$false)] 
        [string[]] 
        $SQLInstance,
        [Parameter(Mandatory=$false)] 
        [string[]] 
        $SQLDB,
        [Parameter(Mandatory=$false)] 
        [string[]] 
        $SQLQuery
        
        )

$connectionString = "Data Source=$SQLInstance;"+"Integrated Security=SSPI;"+"Initial Catalog=$SQLDB"

$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($SQLQuery,$connection)
$connection.Open()

$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null

$connection.Close()
$dataSet.Tables

}






# EXECUTE Query ($SQLQuery)
    Write-Log -Message "Execution of GetSQLData on $SQLDB" -LogPath $LogFolder -LogName $Log
    $Result = 
    Try {
        GetSQLData -SQLInstance $SQLInstance -SQLDB $SQLDB -SQLQuery $SQLQuery
        }
    Catch
        {
        $Message = "ERROR DURING EXECUTION OF QUERY"
        Write-Host -F Red $Message
        Write-Log -Message "$Message - $($Error[0].Exception)" -LogPath $LogFolder -LogName $Log
        Exit 1
        }


########################################
# SOUS-REGROUPEMENTS
########################################


# All SCCM DP
$AllSCCMDP = $Result | ConvertTo-Csv -Delimiter ';' -NoTypeInformation | foreach {$_.replace('"','')}

$AllSCCMDP

# EXPORTS TO TXT FILES
$AllSCCMDP  |  Out-File -FilePath "$ExportFolder\All_SCCMDP.txt" -Force



# DISPLAY SUCCESS
$Message = "--- EXECUTION OK ---"
Write-Host -F Green $Message
Write-Log -Message $Message -LogPath $LogFolder -LogName $Log



 

O365 : Réaliser un hard match (Update)

Dans un précédent article j'expliquais comment réaliser un hard match (https://blog.piservices.fr/post/2021/04/01/o365-realiser-un-hard-match).

J'ai récemment eu à me servir de nouveau des cmdlets et ces dernières ne fonctionnaient pas correctement, il est possible de le faire via API Graph, mais les commande Azure AD permettent encore de le faire si besoin.

 

# 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 Azure AD
Connect-AzureAD

# Retrieve my user
$User = Get-AzureADUser -SearchString "jdupont @mydomain.com"

# Set ImmutableID to user
Set-AzureADUser -ObjectId $User.ObjectID -ImmutableId $ImmutableId

 

Active Directory : Lister tous les comptes dont le mot ne passe n'expire jamais

Dans toute annuaire Active Directory il existe une mauvaise pratique, celle dont je voudrais vous parler aujourd'hui est la non expiration des mots de passe pour un / des comptes du domaine. 

Pourquoi ?

Il existe plusieurs arguments au fait qu'un mot de passe qui n'expire jamais est une mauvaise pratique, je mettrais en avant ici les deux qui me posent le plus problème:

  • Tout d'abord un mot de passe qui n'expire jamais a plus de chance d'être "découvert" dans des attaques de type brute force; si on se concentre sur ces comptes en particulier, le fait qu'il n'y ai pas de rotation de mot de passe, laisse une plus grande période de temps à l'attaquant pour le découvrir.
  • Si le compte est compromis, l'attaquant a par définition un accès "constant" au SI, puisque tant que la rotation du mot de passe n'aura pas lieu, ce dernier conservera son accès.

Que faire ?

Bien qu'il ne soit pas une bonne pratique d'autoriser la non expiration des mots de passe, je les croise tous le jours dans tout Active Directory, avec toujours "une bonne raison" de l'avoir fait.

En revanche, même s'il n'est pas possible de s'en séparer, il est tout de même bon de mettre en oeuvre quelques bonne pratiques lorsque l'on est dans cette situation :

  • Lister les comptes dont le mot de passe n'expire jamais.
  • Documenter la cause de cette configuration.
  • Documenter leur emploi (raison d'utilisation, application dans lesquelles ils sont utilisés, machines sur lesquelles ils sont utilisés).
  • Documenter la date du dernier changement de mot de passe (même si elle peut être retrouvée dans l'AD).
  • Documenter une procédure de changement de mot de passe (documentation applicative, processus de dépendance...).
  • Indiquer une personne / équipe en mesure de pouvoir réaliser le changement de mot de passe (il se peut qu'il y ai des développeurs, prestataires externes, éditeurs qui se servent de ce mot de passe). 
  • Réaliser une rotation du mot de passe manuellement.

Lister les comptes avec Powershell.

# Variables
$RootFolder = "C:\Temp"
$Workefolder = "$RootFolder\NeverExpires"
$LogFolder = "$RootFolder\Logs"
$LogFile = "$LogFolder\NeverExpires.txt"
$AllFolders = $RootFolder, $Workefolder, $LogFolder

# Check and create if needed
foreach ($Folder in $AllFolders) {
    If (!(Test-Path $Folder)) {
        Try {
            New-Item $Folder -ItemType Directory -ErrorAction Stop
            }
        Catch {
            Write-Warning $($_)
            }
        }
    }

# Import module
Try {
    Import-Module ActiveDirectory -ErrorAction Stop
    }
Catch {
    Write-Output "Failed to import module ActiveDirectory" | Add-Content $LogFile
    Exit
    }

# Get Active Directory users that have a password that never expires
Try {
    $AllEnabledUsers = Get-ADUser -Filter {Enabled -eq $true} -Properties PasswordNeverExpires -ErrorAction Stop
    $PasswordNeverExpires = $AllEnabledUsers.Where({$_.PasswordNeverExpires -ne $false})
    }
Catch {
    Write-Output "Failed to collect users" | Add-Content $LogFile
    }

# Export result
$PasswordNeverExpires | Export-Csv "$Workefolder\PasswordNeverExpires.csv" -Delimiter ";" -Encoding UTF8

 

Script Powershell - Directory-TreeSize

Le script ci-dessous est une version modifiée d'un script qui propose une version ligne de commande de l'outil TreeSize pour afficher les tailles récursives d'une arborescence de dossier/Fichiers.

Le script original est disponible ici: http://aka.ms/directory-treesize.ps1

- Une fonction GetSizeToCSV a été ajouté pour generer un export CSV

- Un bloc ValidateScript a été ajouté pour le paramètre Directory

 

<#
.SYNOPSIS
    powershell script to to enumerate directory summarizing in tree view directories over a given size

.DESCRIPTION
    
    .\directory-treesize.ps1 c:\windows\system32

    To enable script execution, you may need to Set-ExecutionPolicy Bypass -Force
         
.NOTES
    File Name  : directory-treesize.ps1
    Version    : 1.0
    History    : 
                CJOURDAN --> Add Function to collect and generate CSV Files (GetSizeToCsv)
                CJOURDAN --> Add ValidateScript for 'directory' parameter validation


.EXAMPLE
    .\directory-treesize.ps1
    enumerate current working directory

.PARAMETER depth
    number of directory levels to display

.PARAMETER detail
    display additional file / directory detail
    output: path, total size of files in path, files in current directory / sub directories, directories in current directory / sub directories 
    example: g:\ size:184.209 GB files:5/98053 dirs:10/19387

.PARAMETER directory
    directory to enumerate

.PARAMETER logFile
    log output to log file

.PARAMETER minSizeGB
    minimum size of directory / file to display in GB

.PARAMETER noColor
    output in default foreground color only

.PARAMETER noTree
    output complete directory and file paths

.PARAMETER quiet
    do not display output

.PARAMETER showFiles
    output file information

.PARAMETER showPercent
    show percent graph

.PARAMETER uncompressed
    for windows file length is used instead of size on disk. this will show higher disk used but does *not* use pinvoke to kernel32
    uncompressed switch makes script pwsh compatible and is enabled by default when path contains '/'
    
.PARAMETER createperfcsv
    Cree un fichier CSV "date;folder;size"

.PARAMETER CsvPerfFile
    Chemin du fichier CSV


#>

[cmdletbinding()]
param(
    
    [Parameter(Mandatory = $true,
               ValueFromPipeline = $true,
               HelpMessage = 'Provide Directory Full Path (Ex: "C:\Temp") ')]
                [ValidateScript({
                                $regex='^\w(:)(\\{1})[^\\].*$'
                                if($_ -notmatch $regex)
                                {
                                $false
                                write-host -B white -F red "$_ N'EST PAS UN CHEMIN DE DOSSIER VALIDE (Ex: 'C:\Temp')"
                                EXIT 1
                                }
                                elseif(!(test-path $_))
                                {
                                $false
                                write-host -B white -F red "LE REPERTOIRE $_ EST INTROUVABLE - VERIFIER LE CHEMIN DU REPERTOIRE"
                                EXIT 1
                                }
                                Else
                                {
                                $true
                                }
                                })]         
                [string]$directory,
    
    [float]$minSizeGB = .01,
    [int]$depth = 99,
    [switch]$detail=$false,
    [switch]$noColor,
    [switch]$notree,
    [switch]$showFiles,
    [string]$logFile,

    [switch]$quiet,
    [switch]$showPercent=$true,
    [switch]$uncompressed,
    [switch]$createperfcsv,
    [string]$CsvPerfFile


)


$timer = get-date
$error.Clear()
$ErrorActionPreference = "silentlycontinue"
$drive = Get-PSDrive -Name $directory[0]
$writeDebug = $DebugPreference -ine "silentlycontinue"
$script:logStream = $null
$script:directories = @()
$script:directorySizes = @()
$script:foundtreeIndex = 0
$script:progressTimer = get-date
$pathSeparator = [io.path]::DirectorySeparatorChar
$isWin32 = $psversiontable.psversion -lt [version]"6.0.0" -or $global:IsWindows

function main()
{
    log-info "$(get-date) starting"
    log-info "$($directory) drive total: $((($drive.free + $drive.used) / 1GB).ToString(`"F3`")) GB used: $(($drive.used / 1GB).ToString(`"F3`")) GB free: $(($drive.free / 1GB).ToString(`"F3`")) GB"
    log-info "enumerating $($directory) sub directories, please wait..." -ForegroundColor Yellow

    $uncompressed = !$isWin32
    [dotNet]::Start($directory, $minSizeGB, $depth, [bool]$showFiles, [bool]$uncompressed)
    $script:directories = [dotnet]::_directories
    $script:directorySizes = @(([dotnet]::_directories).totalsizeGB)
    $totalFiles = (($script:directories).filesCount | Measure-Object -Sum).Sum
    $totalFilesSize = $script:directories[0].totalsizeGB
    log-info "displaying $($directory) sub directories over -minSizeGB $($minSizeGB): files: $($totalFiles) directories: $($script:directories.Count)"

    # si le parametre $createperfcsv est $true Ajout de la collecte au format CSV des données directory et taille avec la date du jour    
    if($createperfcsv)
    {
    GetSizeToCsv -directory $directory -directories $directories
    }



    $sortedBySize = $script:directorySizes -ge $minSizeGB | Sort-Object
        
    if ($sortedBySize.Count -lt 1)
    {
        log-info "no directories found! exiting" -foregroundColor Yellow
        exit
    }

    $categorySize = [int]([math]::Floor([math]::max(1, $sortedBySize.Count) / 6))
    $redmin = $sortedBySize[($categorySize * 6) - 1]
    $darkredmin = $sortedBySize[($categorySize * 5) - 1]
    $yellowmin = $sortedBySize[($categorySize * 4) - 1]
    $darkyellowmin = $sortedBySize[($categorySize * 3) - 1]
    $greenmin = $sortedBySize[($categorySize * 2) - 1]
    $darkgreenmin = $sortedBySize[($categorySize) - 1]
    $previousDir = $directory.ToLower()
    [int]$i = 0

    for ($directorySizesIndex = 0; $directorySizesIndex -lt $script:directorySizes.Length; $directorySizesIndex++)
    {

        $previousDir = enumerate-directorySizes -directorySizesIndex $directorySizesIndex -previousDir $previousDir
        

    }

    log-info "$(get-date) finished. total time $((get-date) - $timer)"
}

function enumerate-directorySizes($directorySizesIndex, $previousDir)
{
    $currentIndex = $script:directories[$directorySizesIndex]
    $sortedDir = $currentIndex.directory
    log-info -debug -data "checking dir $($currentIndex.directory) previous dir $($previousDir) tree index $($directorySizesIndex)"
    [float]$totalSizeGB = $currentIndex.totalsizeGB
    log-info -debug -data "rollup size: $($sortedDir) $([float]$totalSizeGB)"

    switch ([float]$totalSizeGB)
    {
        {$_ -ge $redmin}
        {
            $foreground = "Red"; 
            break;
        }
        {$_ -gt $darkredmin}
        {
            $foreground = "DarkRed"; 
            break;
        }
        {$_ -gt $yellowmin}
        {
            $foreground = "Yellow"; 
            break;
        }
        {$_ -gt $darkyellowmin}
        {
            $foreground = "DarkYellow"; 
            break;
        }
        {$_ -gt $greenmin}
        {
            $foreground = "Green"; 
            break;
        }
        {$_ -gt $darkgreenmin}
        {
            $foreground = "DarkGreen"; 
        }

        default
        {
            $foreground = "Gray"; 
        }
    }

    if (!$notree)
    {
        while (!$sortedDir.Contains("$($previousDir)$($pathSeparator)"))
        {
            $previousDir = "$([io.path]::GetDirectoryName($previousDir))"
            log-info -debug -data "checking previous dir: $($previousDir)"
        }

        $percent = ""

        if ($showPercent)
        {
            if ($directorySizesIndex -eq 0)
            {
                # set root to files in root dir
                $percentSize = $currentIndex.sizeGB / $totalFilesSize
            }
            else 
            {
                $percentSize = $totalSizeGB / $totalFilesSize
            }

            $percent = "[$(('X' * ($percentSize * 10)).tostring().padright(10))]"
        }

        $output = $percent + $sortedDir.Replace("$($previousDir)$($pathSeparator)", "$(`" `" * $previousDir.Length)$($pathSeparator)")
    }
    else
    {
        $output = $sortedDir
    }

    if ($detail)
    {
        log-info ("$($output)" `
            + "`tsize:$(($totalSizeGB).ToString(`"F3`")) GB" `
            + " files:$($currentIndex.filesCount)/$($currentIndex.totalFilesCount)" `
            + " dirs:$($currentIndex.directoriesCount)/$($currentIndex.totalDirectoriesCount)") -ForegroundColor $foreground
    }
    else
    {
        log-info "$($output) `t$(($totalSizeGB).ToString(`"F3`")) GB" -ForegroundColor $foreground
    }

    if ($showFiles)
    {
        foreach ($file in ($currentIndex.files).getenumerator())
        {
            log-info ("$(' '*($output.length))$([int64]::Parse($file.value).tostring("N0").padleft(15))`t$($file.key)") -foregroundColor cyan
        }
    }

   return $sortedDir
}

function log-info($data, [switch]$debug, $foregroundColor = "White")
{
    if ($debug -and !$writeDebug)
    {
        return
    }

    if ($debug)
    {
        $foregroundColor = "Yellow"
    }

    if($noColor)
    {
        $foregroundColor = "White"
    }

    if (!$quiet)
    {
        write-host $data -ForegroundColor $foregroundColor
    }

    if($InformationPreference -ieq "continue")
    {
        Write-Information $data
    }

    if ($logFile)
    {
        if ($script:logStream -eq $null)
        {
            $script:logStream = new-object System.IO.StreamWriter ($logFile, $true)
        }

        $script:logStream.WriteLine($data)
    }
}


function GetSizeToCsv($directory,$directories,$CsvPerfFolder)
{
# Date actuelle
$Now = get-date -Format "dd-MM-yyyy"
# On crée un fichier avec le nom des colonnes. 
$CsvFile = New-Item -Path $CsvPerfFile -Force


"date;folder;size" | Add-Content -Path $CsvFile.FullName
#Pour chacune des lignes de la variable $directories on construis une ligne CSV "date,repertoire,size" qu'on ajoute au fichier
$directories | foreach {Add-Content -Value "$Now;$($_.directory);$($_.totalSizeGB)`n" -Path $CsvFile.FullName} 
}


$code = @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

public class dotNet
{
    [DllImport("kernel32.dll")]
    private static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
        [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

    [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
    private static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
       out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
       out uint lpTotalNumberOfClusters);

    public static uint _clusterSize;
    public static int _depth;
    public static List<directoryInfo> _directories;
    public static float _minSizeGB;
    public static bool _showFiles;
    public static List<Task> _tasks;
    public static DateTime _timer;
    public static bool _uncompressed;
    public static string _pathSeparator = @"\";

    public static void Main() { }
    public static void Start(string path, float minSizeGB = 0.01f, int depth = 99, bool showFiles = false, bool uncompressed = false)
    {
        _directories = new List<directoryInfo>();
        _timer = DateTime.Now;
        _showFiles = showFiles;
        _tasks = new List<Task>();
        _uncompressed = uncompressed;
        _minSizeGB = minSizeGB;

        if(path.Contains("/"))
        {
            _pathSeparator = "/";
        }

        _depth = depth + path.Split(_pathSeparator.ToCharArray()).Count();

        if (!_uncompressed)
        {
            _clusterSize = GetClusterSize(path);
        }

        // add 'root' path
        directoryInfo rootPath = new directoryInfo() { directory = path.TrimEnd(_pathSeparator.ToCharArray()) };
        _directories.Add(rootPath);
        _tasks.Add(Task.Run(() => { AddFiles(rootPath); }));

        Console.WriteLine("getting directories");
        AddDirectories(path, _directories);
        Console.WriteLine("waiting for task completion");

        while (_tasks.Where(x => !x.IsCompleted).Count() > 0)
        {
            _tasks.RemoveAll(x => x.IsCompleted);
            Thread.Sleep(100);
        }

        Console.WriteLine(string.Format("total files: {0} total directories: {1}", _directories.Sum(x => x.filesCount), _directories.Count));
        Console.WriteLine("sorting directories");
        _directories.Sort();
        Console.WriteLine("rolling up directory sizes");
        TotalDirectories(_directories);
        Console.WriteLine("filtering directory sizes");
        FilterDirectories(_directories);

        // put trailing slash back in case 'root' path is root
        if (path.EndsWith(_pathSeparator))
        {
           _directories.ElementAt(0).directory = path;
        }

        Console.WriteLine(string.Format("Processing complete. minutes: {0:F3} filtered directories: {1}", (DateTime.Now - _timer).TotalMinutes, _directories.Count));
        return;
    }

    private static void AddDirectories(string path, List<directoryInfo> directories)
    {
        try
        {
            List<string> subDirectories = Directory.GetDirectories(path).ToList();

            foreach (string dir in subDirectories)
            {
                FileAttributes att = new DirectoryInfo(dir).Attributes;

                if ((att & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
                {
                    continue;
                }

                directoryInfo directory = new directoryInfo() { directory = dir };
                directories.Add(directory);
                _tasks.Add(Task.Run(() => { AddFiles(directory); }));
                AddDirectories(dir, directories);
            }
        }
        catch { }
    }

    private static void AddFiles(directoryInfo directoryInfo)
    {
        long sum = 0;

        try
        {
            DirectoryInfo dInfo = new DirectoryInfo(directoryInfo.directory);
            List<FileInfo> filesList = dInfo.GetFileSystemInfos().Where(x => (x is FileInfo)).Cast<FileInfo>().ToList();
            directoryInfo.directoriesCount = dInfo.GetDirectories().Count();

            if (_uncompressed)
            {
                sum = filesList.Sum(x => x.Length);
            }
            else
            {
                sum = GetSizeOnDisk(filesList);
            }

            if (sum > 0)
            {
                directoryInfo.sizeGB = (float)sum / (1024 * 1024 * 1024);
                directoryInfo.filesCount = filesList.Count;


                if (_showFiles)
                {
                    foreach (FileInfo file in filesList)
                    {
                        directoryInfo.files.Add(file.Name, file.Length);
                    }

                    directoryInfo.files = directoryInfo.files.OrderByDescending(v => v.Value).ToDictionary(x => x.Key, x => x.Value);
                }
            }
        }
        catch { }
    }

    private static void FilterDirectories(List<directoryInfo> directories)
    {
        _directories = directories.Where(x => x.totalSizeGB >= _minSizeGB & (x.directory.Split(_pathSeparator.ToCharArray()).Count() <= _depth)).ToList();
    }

    private static uint GetClusterSize(string fullName)
    {
        uint dummy;
        uint sectorsPerCluster;
        uint bytesPerSector;
        int result = GetDiskFreeSpaceW(fullName, out sectorsPerCluster, out bytesPerSector, out dummy, out dummy);

        if (result == 0)
        {
            return 0;
        }
        else
        {
            return sectorsPerCluster * bytesPerSector;
        }
    }

    public static long GetFileSizeOnDisk(FileInfo file)
    {
        // https://stackoverflow.com/questions/3750590/get-size-of-file-on-disk
        uint hosize;
        string name = file.FullName.StartsWith("\\\\") ? file.FullName : "\\\\?\\" + file.FullName;
        uint losize = GetCompressedFileSizeW(name, out hosize);
        long size;

        if (losize == 4294967295 && hosize == 0)
        {
            // 0 byte file
            return 0;
        }

        size = (long)hosize << 32 | losize;
        return ((size + _clusterSize - 1) / _clusterSize) * _clusterSize;
    }

    private static long GetSizeOnDisk(List<FileInfo> filesList)
    {
        long result = 0;

        foreach (FileInfo fileInfo in filesList)
        {
            result += GetFileSizeOnDisk(fileInfo);
        }

        return result;
    }

    private static void TotalDirectories(List<directoryInfo> dInfo)
    {
        directoryInfo[] dirEnumerator = dInfo.ToArray();
        int index = 0;
        int firstMatchIndex = 0;

        foreach (directoryInfo directory in dInfo)
        {

            if (directory.totalSizeGB > 0)
            {
                continue;
            }

            bool match = true;
            bool firstmatch = false;

            if (index == dInfo.Count)
            {
                index = 0;
            }

            string pattern = string.Format(@"{0}(\\|/|$)", Regex.Escape(directory.directory));

            while (match && index < dInfo.Count)
            {
                string dirToMatch = dirEnumerator[index].directory;

                if (Regex.IsMatch(dirToMatch, pattern, RegexOptions.IgnoreCase))
                {
                    if (!firstmatch)
                    {
                        firstmatch = true;
                        firstMatchIndex = index;
                    }
                    else
                    {
                        directory.totalDirectoriesCount += dirEnumerator[index].directoriesCount;
                        directory.totalFilesCount += dirEnumerator[index].filesCount;
                    }

                    directory.totalSizeGB += dirEnumerator[index].sizeGB;
                }
                else if (firstmatch)
                {
                    match = false;
                    index = firstMatchIndex;
                }

                index++;
            }
        }
    }

    public class directoryInfo : IComparable<directoryInfo>
    {
        public string directory;
        public int directoriesCount;
        public Dictionary<string, long> files = new Dictionary<string, long>();
        public int filesCount;
        public float sizeGB;
        public int totalDirectoriesCount;
        public int totalFilesCount;
        public float totalSizeGB;

        int IComparable<directoryInfo>.CompareTo(directoryInfo other)
        {
            // fix string sort 'git' vs 'git lb' when there are subdirs comparing space to \ and set \ to 29
            string compareDir = new String(directory.ToCharArray().Select(ch => ch <= (char)47 ? (char)29 : ch).ToArray());
            string otherCompareDir = new String(other.directory.ToCharArray().Select(ch => ch <= (char)47 ? (char)29 : ch).ToArray());
            return String.Compare(compareDir, otherCompareDir, true);
        }
    }
}
'@

try
{
    Add-Type $code
    main
}
catch
{
    write-host "main exception: $($error | out-string)"   
    $error.Clear()
}
finally
{
    [dotnet]::_directories.clear()
    $script.directories = $Null

    if ($script:logStream)
    {
        $script:logStream.Close() 
        $script:logStream = $null
    }
}






 

Powershell - Utiliser ValidateScript pour valider une adresse IP en parametre

ValidateScript est un mot clé dans la declaration de paramètres d'un script ou d'une fonction, permettant de valider la valeur d'un paramètre en excutant un bloc de script qui va tester la valeur passée en paramètre. NB: il complète le mot clé ValidatePattern utilisé lui pour valider le paramètre a l'aide d'une expression regulière.

L'exemple ci-dessous est celui de la validation qu'une addresse IP donnée en paramètre est bien au format IPv4. Un message customisé est renvoyé.

 

[CmdletBinding()]
param(

[Parameter(Mandatory=$true,HelpMessage="IP cible")]
          [ValidateScript({
                           $startchar = "^"  # CARACTERE DE DEBUT DE REGEX
                           $endchar = "$"    # CARACTERE DE FIN DE REGEX
                           $ZeroOrOneTime = '?' # CHARACTERE 0 OU 1 FOIS
                           $byte = "(?:25[0-5]|2[0-4][0-9]|[01]$ZeroOrOneTime[0-9][0-9]$ZeroOrOneTime)" # REGEX CORRESPONDANT A UN NOMBRE D'UNE IP
                           $dot = '\.' # CARACTERE '.' 
                           $IPv4 = "$byte$dot$byte$dot$byte$dot$byte" # REGEX COMPLETE D'UNE IPv4
                           if($_ -match "$startchar$IPv4$endchar")
                                {
                                $true
                                } 
                                else 
                                {
                                write-host -B white -F red "$_ N'EST PAS UNE ADDRESSE IPV4 VALIDE. VEUILLEZ RENSEIGNER UNE ADRESSE AU FORMAT X.X.X.X (Ex: 192.168.0.1)"
                                EXIT 1
                                }
                          })]          
$TargetIP
)

Write-Host -F Green "$TargetIP EST UNE VALEUR CORRECTE"

 

Si la valeur renseignée est une adresse IPv4 correcte:

Si la valeur renseignée n'est pas une adresse IPv4 correcte:

 

Le scriptblock éxécuté par ValidateScript peut bien sur être reutilisé comme une fonction a part entière, en dehors du bloc de paramètres.

 

 

 

PowerShell - Récupérer la fréquence d'utilisation des suffixes des userPrincipalName (UPN) dans une forêt Active Directory

#Informations récupérées : suffixe des userPrincipalName (UPN) et fréquence d'utilisations dans les domaines choisis

#Prérequis : l'utilisateur qui lance le script doit pouvoir lire l'Active Directory en PowerShell

#List de paramètres qui permettent de récupérer soit la fréquence des UPN de l'ensemble des domaines Active Directory (AD) de la forêt ou de spécifier les domaines dans lesquels chercher
#Exemple 1 d'utilisation du script : .\Get-UPNSuffixFrequency.ps1 -SearchInAllDomainsOfTheForest
#Exemple 2 d'utilisation du script : .\Get-UPNSuffixFrequency.ps1 -SpecifyDomains customer.intern, technical.intern
Param
( 	
	#Paramètre de type switch qui s'il est utilisé va récupérer la fréquence des suffixes UPN de l'ensemble des domaines AD de la forêt
	[Parameter(Mandatory=$false)]
    [switch]$SearchInAllDomainsOfTheForest,

	#Paramètre de type tableau qui s'il est utilisé va réucupérer la fréquence des suffixes UPN dans les domaines AD mentionnés
    [Parameter(Mandatory=$false)]
    [array]$SpecifyDomains
)

#Importe le module Active Directory qui contient des commandes utilisées dans le script
Import-Module ActiveDirectory

#Si le paramètre de type switch $SearchInAllDomainsOfTheForest est utilisé, récupère les noms de tous les domaines dans la forêt AD
if ($SearchInAllDomainsOfTheForest)
{
	$Domains = Get-ADForest | Select-Object Domains
	$Domains = $Domains.Domains
}

#Si le paramètre de type tableau $SpecifyDomains est utilisé, ajoute les noms des domaines spécifiés comme base de recherche pour la fréquence des suffixes UPN
if ($SpecifyDomains)
{
	$Domains = $SpecifyDomains
}

#Variable globale qui va contenir l'ensemble des UPN des utilisateurs des domaines requêtés
$AllUsersObjects = @()

#Parcours chaque domaine requêté et récupère l'ensemble des UPN des utilisateurs
foreach ($Domain in $Domains)
{
	#Récupére l'ensemble des utilisateurs du domaine actuellement requêté
    $UsersObjectsFromDomain = Get-ADUser -Filter * -Server $Domain -Properties userPrincipalName | Select-Object userPrincipalName

	#Ajoute les utilisateurs du domaine actuellement requêté a la variable globale qui va contenir l'ensemble des UPN des utilisateurs des domaines requêtés
    $AllUsersObjects += $UsersObjectsFromDomain
}

#Commande qui va récupérer l'ensemble des suffixes UPN utilisables dans l'AD
$ADForestObject = Get-ADForest | Select-Object UPNSuffixes

#Créer un tableau qui contiendra l'ensemble des suffixes UPN et leur fréquence
$Array = @()

#Parcours l'ensemble des suffixes UPN dans l'AD et les compare à la liste de l'ensemble des utilisateurs des domaines requêtés
foreach ($UPNSuffixe in $ADForestObject.UPNSuffixes)
{
	#Initialise la fréquence du suffixe UPN actuellement requêté à 0
	$UPNSuffixeOccurence = 0

	#Créer un objet PowerShell qui contiendra le suffixe UPN actuellement requêté et sa fréquence d'utilisation dans les domaines requêtés
    $Line = New-Object PSObject

	#Ajoute à l'object PowerShell précédemment crée, le suffixe UPN actuellement requêté
	$Line | Add-Member -MemberType NoteProperty -Name "UPNSuffixe" -Value $UPNSuffixe

	#Boucle qui va comparer le suffixe UPN actuellement requêté avec chaque utilisateur des domaines requêtés et comptabilise la fréquence d'utilisation
	foreach ($UserObject in $AllUsersObjects)
	{
		#Vérifie que l'UPN de l'utilisateur actuellement requêté n'est pas vide
		if ($UserObject.userPrincipalName)
		{
			#Sépare le préfixe du suffixe UPN de l'utilisateur actuellement requêté
			$UserUPNSuffixe = (($UserObject.userPrincipalName).Split('@'))[1]

			#Compare la valeur du suffixe UPN de l'utilisateur actuellement requêté avec le suffixe UPN actuellement requêté
			if ($UserUPNSuffixe -eq $UPNSuffixe)
			{
				#Si la valeur du suffixe UPN de l'utilisateur actuellement requêté et le suffixe UPN actuellement requêté sont égaux, incrémente de 1 le nombre d'utilisation du suffixe UPN
				$UPNSuffixeOccurence += 1
			}
		}
	}

	#Une fois que l'ensemble des suffixes UPN des utilisateurs des domaines requêtés ont été comparés au suffixe UPN actuellement requêté, ajoute la fréquence du suffixe UPN à l'objet PowerShell précédemment crée
	$Line | Add-Member -MemberType NoteProperty -Name "UPNSuffixeOccurence" -Value $UPNSuffixeOccurence

	#Ajoute à la variable globale qui contient l'ensemble des suffixes UPN et leur fréquence, l'objet PowerShell précédemment crée lors de l'itération courante
    $Array += $Line

	#Supprime les valeurs des variables propres au suffixe UPN actuellement requêté
    Clear-Variable UPNSuffixe, Line
}

#Affiche sous forme de liste tableau l'ensemble des suffixes UPN et leurs fréquences d'utilisation
$Array

<# Exemple d'affichage
UPNSuffixe             UPNSuffixeOccurence
----------             -------------------
customer.intern                         47
technical.intern                        42
#>

 

PowerShell - Vérifier si une mise à jour Windows (KB) est installée sur les contrôleurs de domaine d'une forêt Active Directory

#Informations récupérées : Nom du contrôleur de domaine (DC), domaine Active Directory (AD) d'appartenance, OS, présence ou non des KB recherchées, date d'installation des KB

#Prérequis : l'utilisateur qui lance le script doit pouvoir requêter en remote PowerShell Administrator l'ensemble des DC de la forêt

#Paramètre obligatoire qui doit contenir la liste des KB à récupérer
#Par exemple pour appeler le script avec une liste de KB : .\Get-DomainControllerKB.ps1 -DCHotFixIDs KB5022511, KB4589208
Param
( 	
	[Parameter(Mandatory=$true)]
    [array]$DCHotFixIDs
)

#Importe le module Active Directory qui contient des commandes utilisées dans le script
Import-Module ActiveDirectory

#Permet de récupérer les noms de tous les domaines dans la forêt AD
$Domains = Get-ADForest | Select-Object Domains
$Domains = $Domains.Domains

#Créer un tableau qui contiendra l'ensemble des statuts des KB des DC de chaque domaine de la forêt
$Array = @()

#Parcours chaque domaine AD de la forêt et récupére la liste des DC
foreach ($Domain in $Domains)
{
	#Récupére l'ensemble des DC du domaine AD actuellement requêté
	$DCs = Get-ADDomainController -Server $Domain -Filter * | Select-Object Name, HostName, OperatingSystem

	#Parcours chaque DC du domaine AD actuellement requêté
    foreach ($DC in $DCs)
    {
		#Vérifie pour chaque KB si elle est présente sur le DC actuellement requêté
		foreach ($DCHotFixID in $DCHotFixIDs)
		{
			#Créer un objet PowerShell qui contiendra les informations sur la KB actuellement requêtée du DC actuellement requêté
			$Line = New-Object PSObject

			#Ajoute à l'objet PowerShell précédemment crée, le nom du DC actuellement requêté
			$Line | Add-Member -MemberType NoteProperty -Name "DomainController" -Value $DC.HostName

			#Ajoute à l'objet PowerShell précédemment crée, le domaine AD d'appartenence du DC actuellement requêté
			$Line | Add-Member -MemberType NoteProperty -Name "Domain" -Value $Domain

			#Ajoute à l'objet PowerShell précédemment crée, le système d'exploitation du DC actuellement requêté
			$Line | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $DC.OperatingSystem

			#Ajoute à l'objet PowerShell précédemment crée, le numéro de la KB actuellement requêté du DC actuellement requêté
			$Line | Add-Member -MemberType NoteProperty -Name "HotFixIDSearched" -Value $DCHotFixID

			#Commande qui va vérifier si la KB actuellement requêtée est présente sur le DC actuellement requêté
			$HotFixObject = Get-HotFix -ComputerName $DC.HostName -ID $DCHotFixID -ErrorAction SilentlyContinue | Select-Object Description, HotFixID, InstalledBy, InstalledOn

			#Si la KB actuellement requêtée est PRESENTE sur le DC actuellement requêté, ajoute les informations relatives à son installation dans l'objet PowerShell précédemment créé
			if ($HotFixObject)
			{
				#Ajoute à l'objet PowerShell précédemment crée, le statut présent ou absent de la KB actuellement requêtée du DC actuellement requêté
				$Line | Add-Member -MemberType NoteProperty -Name "KBIsInstalled" -Value "Success"

				#Ajoute à l'objet PowerShell précédemment crée, le nom de l'utilisateur qui a réalisé l'installation de la KB actuellement requêtée du DC actuellement requêté
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixInstalledBy" -Value $HotFixObject.InstalledBy

				#Ajoute à l'objet PowerShell précédemment crée, la date d'installation, arrondi au jour prêt, de la KB actuellement requêtée du DC actuellement requêté
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixInstalledOn" -Value $HotFixObject.InstalledOn

				#Ajoute à l'objet PowerShell précédemment crée, le type de KB actuellement requêté du DC actuellement requêté
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixDescription" -Value $HotFixObject.Description

				#Supprime les valeurs des variables propres à la KB actuellement requêté du DC actuellement requêté
				Clear-Variable HotFixObject
			}

			#Si la KB actuellement requêtée est ABSENTE sur le DC actuellement requêté, ajoute des valeurs Fail ou NULL pour ses informations dans l'objet PowerShell précédemment créé
			else
			{
				$Line | Add-Member -MemberType NoteProperty -Name "KBIsInstalled" -Value "Fail"
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixInstalledBy" -Value "NULL"
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixInstalledOn" -Value "NULL"
				$Line | Add-Member -MemberType NoteProperty -Name "HotFixDescription" -Value "NULL"
			}

			#Ajoute à la variable globale qui contient l'ensemble des statuts des KB des DC de chaque domaine, l'objet PowerShell précédemment crée lors de l'itération courante
			$Array += $Line

			#Supprime les valeurs des variables propres à la KB actuellement requêtée du DC actuellement requêté
			Clear-Variable DCHotFixID, Line
		}

		#Supprime les valeurs des variables propres au DC actuellement requêté
        Clear-Variable DC
    }

	#Supprime les valeurs des variables propres au domaine actuellement requêté
    Clear-Variable Domain, DCs
}

#Affiche sous forme de liste l'ensemble des KB requêtées des DC de chaque domaine avec leurs status
$Array

<#Exemple d'affichage
DomainController  : DC01.customer.intern
Domain            : customer.intern
OperatingSystem   : Windows Server 2022 Standard
HotFixIDSearched  : KB5022511
KBIsInstalled     : Fail
HotFixInstalledBy : NULL
HotFixInstalledOn : NULL
HotFixDescription : NULL

DomainController  : DC01.customer.intern
Domain            : staff.nsi.dir
OperatingSystem   : Windows Server 2022 Standard
HotFixIDSearched  : KB4589208
KBIsInstalled     : Fail
HotFixInstalledBy : NULL
HotFixInstalledOn : NULL
HotFixDescription : NULL

DomainController  : DC02.technical.intern
Domain            : technical.intern
OperatingSystem   : Windows Server 2019 Standard
HotFixIDSearched  : KB5022511
KBIsInstalled     : Success
HotFixInstalledBy : NT AUTHORITY\SYSTEM
HotFixInstalledOn : 15/02/2023 00:00:00
HotFixDescription : Update

DomainController  : DC02.technical.intern
Domain            : technical.intern
OperatingSystem   : Windows Server 2019 Standard
HotFixIDSearched  : KB4589208
KBIsInstalled     : Success
HotFixInstalledBy : NT AUTHORITY\SYSTEM
HotFixInstalledOn : 28/01/2021 00:00:00
HotFixDescription : Update
#>

 

PowerShell - Récupérer la configuration système des contrôleurs de domaine d'une forêt Active Directory

#Informations récupérées : Nom du contrôleur de domaine, domaine AD, adresse IP, OS, CPU, taille du disque C, taille restante du disque C, RAM
#Evolution du script : certains attributs non utilisés sont récupérés pour pouvoir éventuellement augmenter le nombre d'informations récupéré 

#Prérequis 1 : l'utilisateur qui lance ce script doit pouvoir requêter les bases WMI de tout les contrôleurs de domaine (DC) de la forêt
#Prérequis 2 : le script suppose qu'un seul adapteur réseau de chaque DC posséde une ou plusieurs adresses IP
#Prérequis 3 : le script suppose que le système d'exploitation de chaque DC est installé sur C:

#Importe le module Active Directory qui contient des commandes utilisées dans le script
Import-Module ActiveDirectory

#Permet de récupérer le nom des domaines de la forêt de façon dynamique
$Domains = Get-ADForest | Select-Object Domains
$Domains = $Domains.Domains

#Créer un tableau qui contiendra l'ensemble des DC de tout les domaines de la forêt
$DCToGetConfiguration = @()

#Parcours un par un chaque domaine de la forêt
foreach ($Domain in $Domains)
{
	#Pour chaque domaine de la forêt, récupére l'ensemble des DC du domaine
	$DomainControllersFromCurrentDomain = (Get-ADDomainController -Server $Domain -Filter * | Select-Object HostName, Domain, IPv4Address).HostName

	#Pour chaque domaine de la forêt, ajoute l'ensemble des DC du domaine à l'ensemble des DC de la forêt
	$DCToGetConfiguration += $DomainControllersFromCurrentDomain
}

#Créer un tableau qui contiendra chaque DC de la forêt avec sa configuration
$Array = @()

#Parcours un par un chaque DC de chaque domaine de la forêt, récupéré précédemment
foreach ($ServerToGetConfiguration in $DCToGetConfiguration)
{
	#Créer un objet PowerShell qui contiendra la configuration du DC actuellement requêté
    $Line = New-Object PSObject

	#Ajoute à l'objet PowerShell précédemment crée, le nom du DC actuellement requêté
    $Line | Add-Member -MemberType NoteProperty -Name "HostName" -Value $ServerToGetConfiguration

	#Récupére le domaine AD d'appartenance du DC actuellement requêté
	$WMIComputerSystemObject = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ServerToGetConfiguration | Select-Object Name, Domain

	#Ajoute à l'objet PowerShell précédemment crée, le domaine AD d'appartenance du DC actuellement requêté
	$Line | Add-Member -MemberType NoteProperty -Name "Domain" -Value $WMIComputerSystemObject.Domain

	#Récupére l'adresse IP du DC actuellement requêté
	$WMINetworkAdapterConfigurationObjects = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $ServerToGetConfiguration | Select-Object PSComputerName, IPAddress, IPSubnet, DefaultIPGateway, DNSServerSearchOrder

	#La commande Get-WmiObject -Class Win32_NetworkAdapterConfiguration récupére les adaptateurs réseaux, certains d'entre eux ne comportent pas de carte réseau
	#La boucle suivante ne permet de récupérer que la carte réseau qui contient une adresse IP
	#Cette portion du script ne marchera pas si plusieurs cartes réseaux contiennent chacune une IP
	foreach ($NetworkAdapterConfigurationObject in $WMINetworkAdapterConfigurationObjects)
	{
		if ($NetworkAdapterConfigurationObject.IPAddress)
		{
			$Line | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value ([string]($NetworkAdapterConfigurationObject.IPAddress))
		}
	}

	#Récupére le système d'exploitation du DC actuellement requêté
	$WMIOperatingSystemObject = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ServerToGetConfiguration | Select-Object PSComputerName, Caption, BuildNumber

	#Ajoute à l'objet PowerShell précédemment crée, système d'exploitation du DC actuellement requêté
	$Line | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $WMIOperatingSystemObject.Caption
	
	#Récupére les CPU du DC actuellement requêté
    $WMIProcessorObjects = Get-WmiObject -Class Win32_Processor -ComputerName $ServerToGetConfiguration | Select-Object PSComputerName, Name, MaxClockSpeed, Manufacturer, NumberOfCores, NumberOfEnabledCore, NumberOfLogicalProcessors

	#Boucle qui va additioner l'ensemble des CPU du DC requêté pour obtenir le nombre total de CPU
	$CPUNumbers = 0
	foreach ($WMIProcessorObject in  $WMIProcessorObjects)
	{
		$CPUNumbers += 1
	}

	#Ajoute à l'objet PowerShell précédemment crée, le nombre total de CPU du DC actuellement requêté
	$Line | Add-Member -MemberType NoteProperty -Name "CPU_total" -Value $CPUNumbers

	#Récupére l'espace disque du DC actuellement requêté
	$WMILogicalDiskObjects = Get-WMIObject -Class Win32_LogicalDisk -ComputerName $ServerToGetConfiguration | Select-Object PSComputerName, DeviceID, FileSystem, MaximumComponentLength, Size, FreeSpace

	#Boucle qui va parcourir chaque disque du DC requêté et ajoute à l'objet PowerShell précédemment crée, l'espace disque total et restant de C: et l'affiche en GB
	foreach ($WMILogicalDiskObject in $WMILogicalDiskObjects)
	{
		if ($WMILogicalDiskObject.DeviceID -eq "C:")
		{
			$Line | Add-Member -MemberType NoteProperty -Name "Disk_size(GB)" -Value ([math]::truncate(($WMILogicalDiskObject.Size)/1GB))
			$Line | Add-Member -MemberType NoteProperty -Name "Disk_free(GB)" -Value ([math]::truncate(($WMILogicalDiskObject.FreeSpace)/1GB))
		}
	}

	#Récupére les RAM du DC actuellement requêté
	$WMIPhysicalMemoryObjects = Get-WmiObject -Class Win32_PhysicalMemory -ComputerName $ServerToGetConfiguration | Select-Object PSComputerName, BankLabel, Capacity, Manufacturer

	#Boucle qui va additionner l'ensemble des RAM du DC requêté pour obtenir le nombre total de RAM
	$RAMnumbers = 0 
	foreach ($WMIPhysicalMemoryObject in $WMIPhysicalMemoryObjects)
	{
		$RAMnumbers += $WMIPhysicalMemoryObject.Capacity
	}
	
	#Ajoute à l'objet PowerShell précédemment crée, le nombre total de RAM du DC actuellement requêté
	$Line | Add-Member -MemberType NoteProperty -Name "Memory(MB)" -Value ([math]::truncate(($RAMnumbers)/1MB))

	#Ajoute à la variable globale qui contient l'ensemble des configuration des DC, l'objet PowerShell précédemment crée de l'itération courante
	$Array += $Line

	#Supprime les valeurs des variables propres au DC actuellement requêté
	Clear-Variable Line, WMIComputerSystemObject, WMINetworkAdapterConfigurationObjects, WMIOperatingSystemObject, CPUNumbers, WMILogicalDiskObjects, WMIPhysicalMemoryObject
}

#Affiche sous forme de liste l'ensemble des DC avec leurs configurations
$Array

<#Exemple d'affichage
HostName        : DC01.customer.intern
Domain          : customer.intern
IPAddress       : 192.168.1.50
OperatingSystem : Microsoft Windows Server 2019 Standard
CPU_total       : 4
Disk_size(GB)   : 50
Disk_free(GB)   : 30
Memory(MB)      : 16384

HostName        : DC02.technical.intern
Domain          : technical.intern
IPAddress       : 192.168.1.51
OperatingSystem : Microsoft Windows Server 2022 Standard
CPU_total       : 4
Disk_size(GB)   : 100
Disk_free(GB)   : 60
Memory(MB)      : 16384
#>

 

Module Powershell SecretStore - Exemple avec API Crowdstrike

SecretStore est un module Powershell permettant de facilement stocker et gérér des Credentials.

Dans l’exemple ci-dessous nous le mettons en place et l’utilisons pour demander, dans cet exemple, un token d’acces au cloud de l’EDR Crowdstrike.

NB : Le module SecretStore requiert l’installation du module SecretManagement

Comme tout module Powershell, si ils ne peut pas être directement téléchargé en ligne sur un repository avec la commande Install-Module, il est possible de le(s) récupérer sur Github ou encore sur Powershell Galery

(https://www.powershellgallery.com/packages/Microsoft.PowerShell.SecretStore)

(https://www.powershellgallery.com/packages/Microsoft.PowerShell.SecretManagement)

Une fois les module décompressé et stocké dans un des dossier contenant en standard des modules (ex : «C:\Program Files\WindowsPowerShell\Modules»)

NB : Si le module n’est pas stocké dans un chemin déclaré dans la variable $env:PSModulePath, son chemin complet devra être renseigné lors de l’import. Ceci peut être problématique aussi après l’import, dans le cadre de certaines commandes du module. Pour cela il est préférable qu’il soit stocké dans un chemin reconnu par la variable $env:PSModulePath

NB : Pour qu’un module soit correctement reconnu par Powershell, le nom du dossier le contenant doit être identique au nom du fichier psd1 ou psm1 :

 C:\Program Files\WindowsPowerShell\Modules \microsoft.powershell.secretstore

Dans une fenêtre powershell, exécuter Import-module en spécifiant le chemin d’accès aux fichiers indiqué ci-dessous

*****

Import-module microsoft.powershell.secretmanagement

Import-module microsoft.powershell.secretstore

*****

Par défaut le module SecretStore requiert un password pour accéder a chaque au coffre-fort de mot passe. La commande suivante permet de spécifier que l’authentification ne sera pas demandée , uniquement pour l’utilisation actuel (CurrentUser)

 

Set-SecretStoreConfiguration -Scope CurrentUser -Authentication None -Interaction None

La commande demande un password qui ne sera pas redemandé

La commande suivante permet de créer le coffre-fort (vault). Indiquer un nom explicite pour l’usage de ce coffre. (La commande requiert d’indiquer le module SecretStore)

Register-SecretVault -ModuleName microsoft.powershell.secretstore -Name MyVault

A présent que le coffre est créé, nous allons y stocker les informations requise, dans cet exemple, pour récupérer un token Crowdstrike.

Dans une variable de type hashtable ($ApiClient) On renseigne le Client Id, Le Client Secret, et le Hostname (url de l’api). On ‘range’  ($ApiClient) dans le coffre MYVault. (Set-secret)

$ApiClient = @{
    ClientId     = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    ClientSecret = ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    Hostname     = 'https://api.eu-1.crowdstrike.com'
}

Set-Secret -Name MyApiClient -Secret $ApiClient -Vault MyVault

On peut tester a présent que le credential peut être récupéré :

Get-Secret -Name MyApiClient -Vault MyVault -AsPlainText

Et dans notre exemple, on peut maintenant récupérer notre token Crowdstrike

Get-Secret -Name MyApiClient -Vault MyVault -AsPlainText | ForEach-Object { Request-FalconToken @_ }

Notre token doit etre valable

$(Test-FalconToken).token

Les cmdlets du module PSFalcon peuvent maintenant être exécutées, par exemple :

Get-FalconHost -All -Detailed

NB : Les modules powershell SecretStore et SecretManagement peuvent même etre supprimé (Remove-Module). Le coffre crée sera toujours accessible par le compte l’ayant crée.

Script - Exemple de recherche d'ancien fichier de log dans une arborescence de dossier

Le script ci-dessous propose de rechercher dans une liste de dossiers specifiques (expression regulière FolderPattern) des fichiers de logs plus ou moins anciens, selon un seuil prédéfini (TimeDelta), ajoute un statut au tableau selon le cas rencontré, et exporte le resultat en fichier csv.

 

##############################################
### CHECK OLD LOG FILES ###
##############################################


<# 

    .SYNOPSIS 
        VERIFICATION DE LA PRESENCE ET DE L'ANCIENNETE DU LOG LE PLUS RECENT DANS UNE LISTE DE DOSSIER; 
        UN TABLEAU DES DOSSIERS DONT LE LOG LE PLUS RECENT EST PLUS ANCIEN QUE J-$TimeDelta EST AFFICHE ET EXPORTE EN FICHIER CSV.  

    .PARAMETER  
        RootFolder : Chemin du dossier racine
        TimeDelta : Seuil d'ancienneté du fichier de log le plus recent
        FolderPattern: Expression reguliere pour selectionner des noms de sous dossier
        

 
    .EXAMPLE 
     .\Check_Old_Log_Folders.ps1 -RootFolder "C:\MyRootFolder" -TimeDelta 15
#>




[CmdletBinding()]
param(
[Parameter(Mandatory,HelpMessage="Chemin du dossier racine")]
$RootFolder,

[Parameter(Mandatory,HelpMessage="Seuil d'ancienneté du fichier de log le plus recent")]
$TimeDelta,

[Parameter(HelpMessage="Expression reguliere pour selectionner des noms de sous dossier")]
[regex]$FolderPattern = "^(SRV|DEV).*"

)



# Date du jour - TimeDelta
$DateTimeMinus = (get-date).AddDays(-$TimeDelta)

# Verification que RootFolder existe 
if (!(Test-Path -Path $RootFolder))
    {
    write-host -B White -F Red "UNABLE TO FIND $RootFolder - CHECK PATH - END OF SCRIPT"
    EXIT 1
    }


# Creation d'un PSobject pour stocker les futurs valeurs LogFolder,LastLog et Status
$FileTab = @()

# Recuperation des noms de dossier
$Folders = Get-ChildItem -Path $RootFolder -Directory | Where-Object {$_.Name -match $FolderPattern}


# Pour chacun des dossier on cherche le dernier fichier modifié
$Folders | foreach {


$LogFolder = $_.Name
$LastLog = Get-ChildItem -Path $_.FullName | select -Last 1 -Property LastWriteTime -ExpandProperty LastWriteTime

# Si le dossier est vide (pas de Lastlog) le status est KO
 if (!$LastLog) {$status = "KO - DOSSIER VIDE"}

# Si LastLog est plus ancien ou plus recent que $DateTimeMinus  
 switch($LastLog)
    {
        
        {$LastLog -lt $DateTimeMinus} {$status = "KO - OLDER THAN $DateTimeMinus"}
        {$LastLog -gt $DateTimeMinus} {$status = "OK - NEWER THAN $DateTimeMinus"}
        default {$status = "UNKNOWN"}
    }


# remplissage du tableau
$FileTab += New-Object -TypeName psobject -Property @{LogFolder=$LogFolder;LastLog=$LastLog;Status=$status}

}


# Affiche le tableau $FileTab des dossiers dont le fichier le plus recent est plus ancien que $TimeDelta ou pour lequel il n'y a pas de log
Write-Host -F red -B White "LOG FOLDERS DONT LE FICHIER DE LOG LE PLUS RECENT EST PLUS ANCIEN QUE J $TimeDelta OU POUR LESQUELS IL N'EXISTE PAS DE LOG (DOSSIER VIDE)"
$KOFiles = $FileTab | Where-Object {$_.status -like 'KO*'} | select LogFolder,LastLog,Status | Sort LastLog
$KOFiles | ft -AutoSize


# Export CSV du tableau en supprimant les caractere '"'
$KOFilesCSV = $KOFiles | ConvertTo-Csv -Delimiter "," -NoTypeInformation | ForEach {$_.replace('"','')}


# Export du fichier CSV
$KOFilesCSV | Out-File "$($pwd.ProviderPath)\OldLogFolders.csv" -Force
write-host -F Blue -B White "Old Log Folders CSV File Exported as $($pwd.ProviderPath)\OldLogFolders.csv"


# Ouverture du fichier CSV
notepad.exe "$($pwd.ProviderPath)\OldLogFolders.csv"