PI Services

Le blog des collaborateurs de PI Services

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"

 

PowerShell - créer et déléguer des Administrative Units

Une présentation des Administrative Units (AU) a été réalisée dans l'article suivant : Azure - présentation des Administrative Units

La délégation des Administrative Units en interface graphique a été expliquée dans l'article suivant :  Azure - déléguer les Administrative Units

 

Créer et déléguer des Administrative Units en PowerShell

#Import les modules AzureAD et AzureADPreview qui contient les commandes utilisées pour la création des Administrative Units (AU)
#Si certaines commandes ne sont pas reconnues, assurez vous d'avoir le module à jour avec la commande Update-Module
Import-Module AzureAD
Import-Module AzureADPreview

#Initie une connexion avec AzureAD dans la console PowerShell, un compte avec les droits Global Administrator ou Privileged Role Administrator est nécessaire
Connect-AzureAD

#Création d'une nouvelle AU, la commande est sauvegardé dans une variable car l'objectID doit être utilisé pour la délégation
#Par défaut l'affectation des objets à l'AU sera Assigned, pour faire de l'affectation dynamique utiliser les paramètres MembershipType, MembershipRuleProcessingState et MembershipRule 
New-AzureADMSAdministrativeUnit -Description "Test Administrative Unit created with PowerShell" -DisplayName "AdministrativeUnit-test2"

#Les propriétés de l'AU sont récupérées car l'object ID est nécessaire pour faire l'affectation des droits au groupe de délégation dans PIM
$NewAU = Get-AzureADAdministrativeUnit -Filter "displayname eq 'AdministrativeUnit-test2'"

#Création d'un groupe de délégation qui aura la délégation sur l'AU créée
#C'est un groupe de sécurité, la partie mail est donc désactivé (MailEnabled $False) et la partie sécurité est activé (SecurityEnabled $True)
#Le paramètre MailNickname est obligatoire, il est donc recommandé de rentrer la même valeur que le displayName
#Pour pouvoir affecter des rôles via PIM, il est nécessaire d'activer le paramètre IsAssignableToRole
#Le paramètre Visibility permet de modifier qui est autorisé à voir les membres du groupe, il est recommandé de choisir soit Private soit HiddenMembership
New-AzureADMSGroup -DisplayName "Group-to-manage-AdministrativeUnit-test2" -Description "Group that have the delegation on the AU AdministrativeUnit-test2" `
-MailEnabled $False -MailNickname "Group-to-manage-AdministrativeUnit-test2" -SecurityEnabled $True -IsAssignableToRole $True -Visibility "Private"

#L'affectation des droits au groupe de délégation dans PIM via la commande New-AzureADMSRoleAssignment nécessite l'ID du groupe de délégation
$NewAzureGroup = Get-AzureADGroup -Filter "displayname eq 'Group-to-manage-AdministrativeUnit-test2'"

#L'affectation des droits au groupe de délégation dans PIM via la commande New-AzureADMSRoleAssignment nécessite l'ID du rôle
#Cet ID peut être récupérer depuis portal.azure.com > Azure Active Directory > Administrative Units > {Cliquer sur une AU existante} >
#Roles and admininistrators > {Choisir le rôle a affecté} > Description > Template ID
#Le template ID choisi est celui d'User administrator
$RoleDefinitionID = Get-AzureADMSRoleDefinition -ID "fe930be7-5e62-47db-91af-98c3a49a38b1"

#L'affectation des droits au groupe de délégation dans PIM via la commande New-AzureADMSRoleAssignment nécessite l'ID de l'AU
$DirectoryScopeId = '/administrativeUnits/' + $NewAU.ObjectId

#Création de la délégation PIM du groupe Group-to-manage-AdministrativeUnit-test2 sur l'AU AdministrativeUnit-test2
New-AzureADMSRoleAssignment -DirectoryScopeId $DirectoryScopeId -RoleDefinitionId $RoleDefinitionID.Id -PrincipalId $NewAzureGroup.ObjectId

 

Vérification de la création et de la délégation

L'Administrative Unit a bien été créé

 

Le groupe de délégation ainsi que son affectation dans PIM est effective

 

Remarque : lors de l'installation du module AzureADPreview, celui-ci entre en conflit avec le module AzureAD s'il est déjà installé, lors de l'utilisation de la commande Install-Module, utiliser les paramètres AllowClobber et Force

 

Pour aller plus loin

Documentation officielle de la commande New-AzureADMSAdministrativeUnit
Documentation officielle de la commande Get-AzureADAdministrativeUnit
Documentation officielle de la commande New-AzureADMSGroup
Documentation officielle de la commande Get-AzureADGroup
Documentation officielle de la commande Get-AzureADMSRoleDefinition
Documentation officielle de la commande New-AzureADMSRoleAssignment

Azure - déléguer les Administrative Units

Une présentation des Administrative Units (AU) a été réalisée dans l'article suivant : Azure - présentation des Administrative Units

 

Créer et déléguer une Administrative Unit

Lorsqu'une Administrative Unit est en phase de création, il est proposé de réaliser la délégation via Privileged Identity Management (PIM).

Création d'une Administrative Unit

 

Choix du nom de la nouvelle AU

 

En bas de la blade choisir Next: Assign roles >

Affecter les rôles qui doivent être délégués à des utilisateurs.

Remarque : il n'est pas possible d'affecter des groupes depuis cette blade, néanmoins il est possible de le faire après que l'AU soit créé en l'éditant.

Dans l'exemple suivant, le rôle User Administrator sera affecté

 

Une fois l'affectation faite, finir la création en cliquant sur Next: Review + create >

 

Puis Create

 

L'Administrative Unit créé est alors affiché

 

Déléguer une Administrative Unit à un groupe

Cliquer sur le nom de l'AU précédemment créée, puis sous Manage choisir la blade Roles and administrators et clique sur le rôle a délégué

 

La blade affichée n'est plus la même que lors de la création de l'AU, c'est la blade de délégation PIM qui s'affiche. Cliquer sur Active assignments puis Add assignments

 

Cliquer sur No member selected et choisir le groupe à déléguer

 

Cliquer sur Next

 

Choisir le mode d'affectation, il est recommandé de choisir un Assignment type Active avec une durée d'affectation Permanently assigned étant donné que les AU sont déjà restreintes en termes de périmètre et des opérations disponibles.

 

Clique sur Assign

 

Les affectations sont visibles depuis le sous-menu PIM précédémment évoqué

 

Les délégations des Administrative Units les plus utiles en septembre 2022

 

Besoin Rôle
Réinitialisation de mot de passe Helpdesk administrator (ne permet pas de reset des mots de passes de comptes à privilèges) ou Password administrator
Edition d'utilisateur User administrator
Gestion des methodes d'authentifications utilisateurs (MFA/SSPR) Authentication Administrator (confére également les droits User administrator)
Gestion des groupes Groups administrator
Gestion des ordinateurs Cloud device administrator
Gestion des équipements Teams Teams devices administrator

 

Remarque : il n'est pas possible de créer des utilisateurs directement depuis une AU

Remarque 2 : les autres rôles donnent soient trop de permissions et ne sont pas conseillés, car proche des droits sur tout le tenant soit n'ont aucune répercussion de délégation.

 

Pour aller plus loin

Sur le même blog : PowerShell - créer et déléguer des Administrative Units

Azure - présentation des Administrative Units

Les Administratives Unites (AU) sont similaires aux Organisational Units dans l'Active Directory OnPremises. Ce sont des containers logiques qui permettent de grouper des utilisateurs, des ordinateurs ou des groupes. L'affectation de ces objets peut être faie de 3 manières différentes : assigned, dynamic user et dynamic device. Les Administrative Units sont encore en cours de développement, mais quelques fonctionalités sont d'ores et déjà disponibles.

 

Les prérequis des Administrative Units

Une licence Azure AD Premium P1 est requise pour chaque utilisateur qui administre une AU.
Une licence Azure AD Free est nécessaire pour chaque membre d'une AU.

Dans le cas où une règle d'inclusion dynamique serait utilisée, chaque membre de l'ADU doit avoir une licence Azure AD Premium P1.

Pour pouvoir crééer, modifier ou supprimer des AU, il est nécessaire d'avoir soit le rôle Azure Global Administrator soit Privileged Role Administrator.

 

Accéder aux Administrative Units

Les Administrative Units peuvent être gérer via portal.azure.com > Azure Active Directory > Administrative Units

Depuis cette blade, il est possible de consulter la liste des Administrative Units déjà créés et de les ouvrir pour voir ce qu'elles contiennent. Il est également possible d'en créer de nouvelles avec le bouton Add.

Une fois dans une Administrative Units la blade Users est sélectionnée par défaut, l'ensemble des utilisateurs contenus dans l'AU sont listés au millieu de l'écran. Dans le menu Manage plusieurs blades sont disponibles

La blade Properties permet de choisir le nom de l'AU, sa description et la façon dont les objets sont provisionnés dans l'AU.

Les blades Groups et Devices listent respectivement les groupes et les ordinateurs de l'AU.

La blade Roles and administrators permet d'affecter via PIM (Privileged Identity Management) des délégations sur l'AU.

Certains rôles, bien qu'ils puissent être affectés n'ont pas d'incidence sur les délégations des AU.

La blade Dynamic membership rules apparaît si l'affectation des objets à l'AU est dynamique, elle permet de choisir les règles d'inclusions des objets dans l'AU.

 

Pour aller plus loin

Sur le même blog : Azure - déléguer les Administrative Units

Sur le même blog : PowerShell - créer et déléguer des Administrative Units

Scripting - Exemple de la recuperation de la date de modification du password d'un compte AD

Problématique:  Récuperer et rendre lisible un timestamp représentant la date de modification du password d'un compte AD, renvoyé par l'outil Dsquery (https://ss64.com/nt/dsquery.html)

 

$user = "johndoe"
$domain = "mydomain.com"

# Executer la commande dsquery pour recuperer l'attribut pwdLastSet
$obj = dsquery * -filter "samaccountname=$user" -attr displayName pwdLastSet -d $domain

# Decouper $obj pour ne recuperer que la chaine correspondant au timestamp
$TimeStamp = $($obj[1] -split " ") | Where-Object {$_ -match '^\d+$'}

# Convertir le timestamp en date avec l'outil w32tm.exe
w32tm.exe /ntte $TimeStamp



Intune : Résoudre les problèmes OOBE Autopilot

Description du problème

L'ordinateur démarre le déploiement Autopilot mais dans l'étape de configuration « Compte », cet écran s'affiche :


Au lieu de cet écran :


--> Si l'utilisateur final choisit "Configurer pour un usage personnel", il sera invité à créer un compte local/compte personnel Microsoft, au lieu d'utiliser son compte Entreprise
--> Par conséquent, l'ordinateur ne rejoindra pas Azure AD et ne sera pas inscrit à Intune

Résultats de troubleshooting

Collectez les journaux de diagnostic de l'ordinateur en exécutant la commande cmd ci-dessous localement (en tant qu'administrateur) :
"MDMDiagnosticsTool.exe -area Autopilot -cab C:\temp\Autopilot.cab"
Une fois généré, ouvrez le fichier compressé et recherchez "microsoft-windows-moderndeployment-diagnostics-provider-autopilot.evtx"
Dans ce journal d'observateur d'événements, vous devriez trouver les événements d'information et d'avertissement ci-dessous :

- Information event ID 153 :

- Warning event ID 100 :

Suite à cet article Microsoft, ces ID d'événement sont expliqués comme suit :

Troubleshoot Autopilot OOBE issues

Conclusion

Selon le troubleshooting, le problème décrit est lié à une erreur temporaire lorsque l'appareil essaie ou attend le téléchargement d'un profil de déploiement Autopilot.

Cette erreur temporaire peut être le résultat de :

- Problème de filtrage de connexion Internet

- Un profil Autopilot qui n'est toujours pas attribué à l'appareil :

  • Le Hash de l'appareil est correctement importé mais l'adhésion au groupe dynamique pour l'attribution du profil Autopilot est toujours en cours (nous pouvons attendre environ une heure après l'import du csv contenant le Hash et avant de commencer le déploiement de l'appareil, pour nous assurer que tout est bien configuré du côté Intune Autopilot)
  • Le Hash de l'ordinateur n'a pas été correctement importé dans Intune, colonne Group Tag manquante par exemple

Navigateur : GPO pour configurer le mode IE dans Edge

Microsoft Edge intègre un mode IE (mode Internet Explorer) qui permet de s'appuyer sur les composants d'IE pour ouvrir certains anciens sites qui ne fonctionnent pas sur les nouveaux navigateurs.

Ci-dessous les étapes pour activer le mode IE dans Microsoft Edge via GPO :

1. Récupérer les fichiers ADMX

Si ce n'est pas déjà le cas, il faut récupérer les fichiers ADMX d'Edge et les intégrer dans votre contrôleur de domaine.

Les fichiers ADMX peuvent être téléchargés ici : Fichiers ADMX d'Edge

2. Configurer IE Mode en GPO

Dans l'éditeur de stratégie de groupe, créez une nouvelle GPO

Sur le dossier "Microsoft Edge" sous "Configuration ordinateur" > "Stratégies" > "Modèles d'administration", recherchez le paramètre "Configurer l'intégration d'Internet Explorer". Activez ce paramètre et choisissez le mode d'intégration "Mode Internet Explorer"

Si vous activez uniquement ce paramètre, le mode Internet Explorer sera automatiquement utilisé pour les sites de votre zone Intranet. Si vous désirez l'utiliser sur des sites externes et gérer une liste de sites, il faut activer le paramètre "Configurer la liste des sites en Mode Entreprise".

Il faut également spécifier le chemin vers le fichier XML qui contient la liste des sites

Afin de vérifier que la GPO s'applique bien, dans Edge, vous pouvez accéder à ce lien dans le navigateur Edge: edge://policy

3. Générer le fichier XML des sites pour IE Mode

Pour créer le fichier XML avec la liste des sites, vous pouvez utiliser ce code de base :

<site-list version="4">
   <created-by>
      <tool>EMIESiteListManager</tool>
      <version>12.0.0.0</version>
      <date-created>07/19/2022 14:03:04</date-created>
   </created-by>
   <site url="www.piservice.fr">
      <compat-mode>Default</compat-mode>
      <open-in>IE11</open-in>
   </site>
   <site url="blog.piservices.fr">
      <compat-mode>Default</compat-mode>
      <open-in>IE11</open-in>
   </site>
</site-list>

Il y a un outil pour générer ce code XML qui s'appelle "Enterprise Mode Site List Manager" qui peut être téléchargé ici: EMSLM

Une fois installé, ouvrez l'outil et cliquez sur le bouton "Add", vous pouvez ajouter un site à la liste et indiquer les paramètres qui s'appliquent pour ouvrir ce site. Lorsque la liste est prête, cliquez sur le bouton "File" puis "Save to XML".

Lorsque vous accédez à un site de cette liste dans Edge, une icône IE apparaîtra dans la barre d'adresse pour vous indiquer qu'il s'exécute via le mode IE, tout en étant toujours dans Edge.

Documentation officielle :

MECM : Paramètres de langue dans SUP pour Windows 11 Servicing (déploiement Feature Updates)

Contrairement aux versions précédentes de Windows, les Feature Updates de Windows 11 dans MECM Servicing nécessitent de définir toutes les langues nécessaires dans le point de mise à jour logicielle. Sinon, ils seront synchronisés mais pas téléchargés.

Configuration classique pour Windows 10

Prenant un exemple où on souhaite télécharger les Feature updates Windows 10 en Anglais, Français, Italien, Allemand et Espagnol :

--> Uniquement deux langues cochées dans la configuration SUP, mais les Feature Updates Windows 10 peuvent être synchronisés et téléchargés en plusieurs langues.

Configuration pour Windows 11

Le Windows Servicing dans MECM ne télécharge les maj d'upgrade Windows 11 que si les langues sont configurées dans SUP.

Prenant le même exemple ci-dessus (Anglais, Français, Italien, Allemand et Espagnol) :

Si on ne coche que Français et Anglais, les Feature Updates Windows 11 seront synchronisés en plusieurs langues mais ne peuvent être téléchargés que en langue française et anglaise.

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