PI Services

Le blog des collaborateurs de PI Services

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

 

SCOM : Management Server installation failed.

Contexte

Lors du déploiement d'un Management Server (SCOM) sur un nouveau serveur, je me suis retrouvé confronté à une erreur lors de l'installation, ce dernier détectait que SCOM était déjà installé sur le serveur alors que ce dernier avait fraichement déployé.

Après avoir vérifié que ni SCOM, ni l'agent était installé sur le serveur en question, j'ai parcouru les logs d'installation à la recherche d'une information qui pourrait m'aider, malheureusement rien et, idem dans l'Event Viewer.

Après de multiples tentatives et analyse, nous avons redéployer un serveur tout neuf mais, avec le même résultat.

Finalement en analysant l'ensemble du process, je me suis aperçu que l'agent avait été déployé durant l'installation du serveur, puis désinstallé manuellement, par conséquent l'agent n'était plus présent sur le serveur mais, certaines clés de registre elles l'étaient encore.

 

Solution

Il  a donc fallu vérifier qu'aucune de ces clés n'étaient présentes et, supprimer celles qui l'étaient :

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Operations Manager
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\System Center Operations Manager
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\System Center
  • HKEY_LOCAl_MACHINE\System\CurrentControlSet\Services\MOMConnector
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Healthservice

Et également supprimer le dossier faisant référence à SCOM sous l'arborescence ci dessous : 

  • HKEY_CLASSES_ROOT\Installer\Products

Une fois ce nettoyage réalisé, un redémarrage du serveur fut nécessaire et l'installation a pu être réalisé avec succès. 

Powershell : Lister les membres externes des sites Sharepoint

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 sites Sharepoint 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 et identifiants (L2, L21, L74 et L88).

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

# Define variables
$ArraySP = @()
$i = 0
$x = 0

# Get the list of All 
$AllSPSite = Get-SPOSite -Limit All
$AllSPSite = $AllSPSite | sort Url

$AllSPSite | foreach {
    $I++
    $Url = $_.Url
    $Owner = $_.Owner
    $Title = $_.title

    # If you are not in the Admin Group you will need to Grant your account as Admin
    Try {
        Set-SPOUser -Site $Url -LoginName "mathieu@contoso.com" -IsSiteCollectionAdmin:$true -ErrorAction Stop
        $Result = $true
        }
    Catch {
        Write-Warning $($_)
        Write-Output $Url | Add-Content C:\temp\SPError.txt
        $Result = $false
        }

    # If you're Admin you can check
    If ($Result -eq $true) {
        # Check Guest Access
        Try {
            # Get Members
            $Members = Get-SPOUser -Site $Url -Limit All -ErrorAction stop
            $External = $Members.where({$_.Usertype -eq "Guest"})

            If ($External.count -ne 0) {
                Write-Host "External found on $Title" -ForegroundColor Cyan
                $External.count
                $External | ForEach {
                    $DisplayName = $_.DisplayName
                    $LoginName = $_.LoginName
                    $Groups = $_.Groups

                    # Store Data
                    $ArraySP += New-Object psobject -Property @{
                        Url = $Url
                        Owner = $Owner
                        Title = $title
                        DisplayName = $DisplayName
                        LoginName = $LoginName
                        Groups = $Groups
                        }
                    # Release
                    $DisplayName = $null
                    $LoginName = $null
                    $Groups = $null
                    }
                }
            # Release
            $Members = $null
            $External = $null
            }
        Catch {
            Write-Warning $($_)
            Write-Host "$Title not Accessible" -ForegroundColor Yellow
            Write-Host "$Url not Accessible" -ForegroundColor Magenta
            Write-Output $Url | Add-Content C:\temp\SPError.txt
            }
        
        # Remove Admin Rights
        Try {
            Set-SPOUser -Site $Url -LoginName "mathieu@contoso.com" -IsSiteCollectionAdmin:$false
            }
        Catch {
            Write-Warning $($_)
            Write-Output $Url | Add-Content C:\temp\SPError.txt
            }
        }
    
    # Release
    $Url = $null
    $Owner = $null
   
    # After 200 connection, reconnect to Sharepoint
    If ($x -eq 200) {
        Connect-SPOService -Url https://contoso-admin.sharepoint.com # (Please change the connection URL)
        $x = 0
        }
    $x++
    }

$ArraySP | Export-Csv c:\temp\ExternalSharepointMembers.csv -Encoding UTF8 -Delimiter ";" -NoTypeInformation

 

[Powershell] - Durcissement Windows Server 2016

Ce script permet de désactiver les éléments non essentiels au serveur lors de son installation.

##################################
# Step 1 : Network configuration #
##################################
Write-Host 'Network configuration' -ForegroundColor Green
Write-Host 'WINS configuration' -ForegroundColor Gray
$Arguments = New-Object System.Collections.Hashtable
$Arguments.Add('DNSEnabledForWINSResolution', $false)
$Arguments.Add('WINSEnableLMHostsLookup', $false)

$ErrorCode = (Invoke-CimMethod -ClassName Win32_NetworkAdapterConfiguration -MethodName EnableWINS -Arguments $Arguments).ReturnValue
If ($ErrorCode -gt 0) {
	Write-Host 'Error when disabling WINS' -ForegroundColor Red
    }

############################
# Step 2 : Remove Features #
############################

Write-Host 'Features configuration' -ForegroundColor Green
$Features = @(
    'FS-SMB1'
    )
$Features | ForEach-Object { 
    $CurrentFeature = $_
    Try {
        Uninstall-WindowsFeature -Name $CurrentFeature -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    # Release
    $CurrentFeature = $null
    }

######################################
# Step 3 : Disabling Scheduled Tasks #
######################################

Write-Host 'Scheduled Tasks configuration' -ForegroundColor Green
$ScheduledTasks = @(
    '*XblGameSaveTask*'
    '*XblGameSaveTaskLogon*'
    )
$ScheduledTasks | ForEach-Object {
    $CurrentScheduledTask = $_
    Try {
        Get-ScheduledTask -TaskName $CurrentScheduledTask | Disable-ScheduledTask -ErrorAction Stop | Out-Null
        }
    Catch {
        Write-Warning $($_)
        }
    $CurrentScheduledTask = $null
    }

################################
# Step 4: Windows Applications #
################################

Write-Host 'Windows Applications configuration' -ForegroundColor Green
$Apps = Get-AppxPackage -AllUsers | Where-Object {$_.NonRemovable -eq $false}
$Packages = Get-AppxProvisionedPackage -Online:$true

# Verifying if some Apps could be remove
If ($Apps.count -gt 0) {
    $Apps | ForEach-Object {
        $AppName = $_.Name
        Try {
            $Package = Get-AppxPackage -Name $AppName -ErrorAction Stop
            If ($Package) {
                Try {
                    # Remove App
                    Write-Host "Removing $AppName" -ForegroundColor Cyan
                    #Remove-AppPackage -Package $Package -ErrorAction Stop
                    $Package = $null
                    }
                Catch {
                    Write-Warning $($_)
                    }
                }
            Else {
                If ($Packages.Count -gt 0) {
                    # Remove the app with Online Package
                    $Package = $Packages.Where({$_.DisplayName -eq $AppName})
                    If ($Package) {
                        Try {
                            Write-Host "Removing $AppName" -ForegroundColor DarkCyan
                            Remove-AppxProvisionedPackage -PackageName $Package.PackageName -Online -ErrorAction Stop
                            }
                        Catch {
                            Write-Warning $($_)
                            }
                        }
                    }
                }
            }
        Catch {
            Write-Warning $($_)
            }
        # Release
        $AppName = $null
        }
    }

###################################
# Step 5 : OneDrive configuration #
###################################

Write-Host 'OneDrive configuration' -ForegroundColor Green
If (Get-Process -Name "*onedrive*") {
    Try {
        Get-Process -Name "*onedrive*" | Stop-Process -Force -ErrorAction Stop
        If (Test-Path -Path "$env:SystemRoot\SysWOW64\OneDriveSetup.exe") {
            Try {
                Start-Process -FilePath "$($env:SystemRoot)\SysWOW64\OneDriveSetup.exe" -ArgumentList '/uninstall' -ErrorAction Stop | out-Null
                }
            Catch {
                Write-Warning $($_)
                }
            }
        Elseif (Test-Path -Path "$env:SystemRoot\System32\OneDriveSetup.exe") {
            Try {
                Start-Process -FilePath "$($env:SystemRoot)\System32\OneDriveSetup.exe" -ArgumentList '/uninstall' -ErrorAction Stop | out-Null
                }
            Catch {
                Write-Warning $($_)
                }
            }
        }
    Catch {
        Write-Warning $($_)
        }
    }
Else {
    Write-Host "No Process OneDrive here" -ForegroundColor Cyan
    }

###################################
# Step 6 : Services configuration #
###################################

$services = @(
    'AppVClient';
    'AudioEndpointBuilder';
    'Audiosrv';
    'AxInstSV';
    'Browser';
    'bthserv';
    'CDPUserSvc';
    'CscService';
    'dmwappushservice';
    'FrameServer';
    'icssvc';
    'lfsvc';
    'lltdsvc';
    'MapsBroker';
    'NcbService';
    'NetTcpPortSharing';
    'OneSyncSvc';
    'PcaSvc';
    'PhoneSvc';
    'PrintNotify';
    'QWAVE';
    'RemoteAccess';
    'RmSvc';
    'SCardSvr';
    'ScDeviceEnum';
    'SensorDataService';
    'SensorService';
    'SensrSvc';
    'SharedAccess';
    'ShellHWDetection';
    'Spooler';
    'SSDPSRV';
    'stisvc';
    'TabletInputService';
    'tzautoupdate';
    'UevAgentService';
    'upnphost';
    'WalletService';
    'WiaRpc';
    'wisvc';
    'wlidsvc';
    'WpnService';
    'WSearch';
    'XblAuthManager';
    'XblGameSave'
    )

$services | Get-Service | Stop-Service -Force

$services | Get-Service | Set-Service -StartupType Disabled -Status Stopped

Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NgcCtnrSvc" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }
Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NgcSvc" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }
Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\PimIndexMaintenanceSvc" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }
Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\UnistoreSvc" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }
Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\UserDataSvc" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }
Try {
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\WpnUserService" -Name "Start" -Value 0x4 -Force -ErrorAction Stop
    }
Catch {
    Write-Warning $($_)
    }

$services2 = @(
    'NgcCtnrSvc';
    'NgcSvc';
    'PimIndexMaintenanceSvc';
    'UnistoreSvc';
    'UserDataSvc';
    'WpnUserService';
    )

$services2 | Get-Service | ft Name,StartType,Status

If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NgcCtnrSvc_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NgcCtnrSvc_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }


If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NgcSvc_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NgcSvc_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }


If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\PimIndexMaintenanceSvc_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\PimIndexMaintenanceSvc_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }


If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\UnistoreSvc_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\UnistoreSvc_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }


If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\UserDataSvc_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\UserDataSvc_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }


If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\WpnUserService_*") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\WpnUserService_*' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }

If (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\xbgm") {
    Try {
        Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\xbgm' -Name 'Start' -Value 0x4 -Force -ErrorAction Stop
        }
    Catch {
        Write-Warning $($_)
        }
    }

 

[Office 365] - Forcer la création de OneDrive

Par défaut lorsque vous créez un utilisateur sur Azure AD et que vous lui attribuez une licence Office (F3, E3, E5) le OneDrive n'est pas créé directement; en effet ce dernier n'est provisionné que lorsque l'utilisateur cliquera sur Onedrive lors de sa connexion.

Lorsque vous souhaitez réaliser une migration O365 ce sujet peut être une problématique, car si les Onedrive ne sont pas créés vous pourrez difficilement lancer une migration en amont.

Il est possible de contourner ce sujet en utilisant les commandes Powershell suivantes (avec les droits minimum de "SharePoint Online administrator") :

# Conect To Sharepoint Online
Connect-SPOService -Credential $Credential -Url https://XXXXX-admin.sharepoint.com

Prenez soin de remplacer l'url de connexion par la votre.

Ensuite vous pouvez soit fournir une liste d'utilisateur, soit récupérer l'ensemble des utilisateurs possédant une licence (ici nous importons une liste) et, lancer les requêtes de création.

# Import Users
$users = Import-Csv C:\Temp\Users.csv 

# Start Request
foreach ($u in $users) {
    $upn = $u.userprincipalname
    $list += $upn

    if ($i -eq 199) {
        #We reached the limit
        Request-SPOPersonalSite -UserEmails $list -NoWait
        Start-Sleep -Milliseconds 655
        $list = @()
        $i = 0
    }
}

 

[Powershell] - Etat des lieux des licences O365

Besoin d'avoir un état des lieux de vos licences O365 (voir de l'automatiser) ?

Voici quelques lignes Powershell qui vous remontrons les informations.

Prérequis:

Vous aurez besoin de:

  • Powershell 5.1
  • Le module Azure AD

Code :

Connect-AzureAD
$Array = @()
$AllSKU = Get-AzureADSubscribedSku

$AllSKU | sort SkuPartNumber | foreach {
    $SkuPartNumber = $_.SkuPartNumber
    $Bought = $_.PrepaidUnits.enabled
    $Suspended = $_.PrepaidUnits.Suspended
    $Warning = $_.PrepaidUnits.Warning
    $Assigned = $_.ConsumedUnits
    $Rest = $Bought - $Assigned

    $Array += New-Object psobject -Property @{
        License = $SkuPartNumber
        Bought = $Bought
        Assigned = $Assigned
        Will_Expired = $Warning
        Suspended = $Suspended
        Rest = $Rest
        }
    $SkuPartNumber = $null
    $Bought = $null
    $Assigned = $null
    $Warning = $null
    $Suspended = $null
    $Rest = $null
        
    }

$Array | FT License,Bought,Assigned,Will_Expired,Suspended,Rest
$Array | Export-Csv C:\temp\Licenses.csv -Delimiter ";" -Encoding UTF8 -NoTypeInformation

 

Bien entendu, pour l'automatiser il faudra modifier l'authentification et passer par de la moderne authentification (via un SPN).

[O365] - Extraire la liste des OneDrive qui sont partagés avec des externes.

Si un jour vous souhaitez faire un état des lieux de ce qui est déjà en place en terme de partage, voici quelques liges de Powershell qui permettent de sortir la liste des OneDrive ayant un partage avec un invité.

Prenez soin de remplacer l'url de connexion au portail d'admin Sharepoint.

# Connect to Sharepoint Online
Connect-SPOService -Url https://MonTenant-admin.sharepoint.com/

# Collect all Sharepoint Sites and filter on OneDrive
$AllOneDrive = Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Url -like '-my.sharepoint.com/personal/'"

# Define variabes 
$ArrayOneDriveMembers = @()
$SortOneDrive = $AllOneDrive | sort Url

$SortOneDrive | foreach {
    $OneDriveUrl = $_.Url
    $OneDriveOwner = $_.Owner
    $OneDriveStorageQuota = $_."Storage Quota"
    $OneDriveTitle = $_.Title
    # Get the list of members for the current OneDrive
    Try {
        $OneDriveMembers = Get-SPOUser -Site $OneDriveUrl -ErrorAction stop
        }
    Catch {
        Write-Output "$OneDriveUrl;$OneDriveTitle" | Add-Content C:\temp\Onedriveerrors.txt
        }
    
    # Define a new variable
    $OneDriveExternal = $OneDriveMembers.where({$_.Usertype -eq "Guest"})

    # Check if the new variable is empty, if not collect logs
    If ($OneDriveExternal.count -ne 0) {
        
        # Display some informations
        Write-Host "External Users are present in $OneDriveTitle" -ForegroundColor Green
        $OneDriveExternal.count

        # Store Data
        $OneDriveExternal | foreach {
            $DisplayName = $_.DisplayName
            $LoginName = $_.LoginName
            
            $ArrayOneDriveMembers += New-Object psobject -Property @{
                OneDriveUrl = $OneDriveUrl
                OneDriveOwner = $OneDriveOwner
                OneDriveStorageQuota = $OneDriveStorageQuota
                OneDriveTitle = $OneDriveTitle
                DisplayName = $DisplayName
                LoginName = $LoginName
                }
            # Release Variables
            $DisplayName = $null
            $LoginName = $null
            }
        }
    
    # Release variables
    $OneDriveUrl = $null
    $OneDriveOwner = $null
    $OneDriveStorageQuota = $null
    $OneDriveTitle = $null
    $OneDriveMembers = $null
    }

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

 

[Azure Active Directory] - Définir le lieu d'utilisation

Lorsque vous synchronisez vos utilisateurs via "Azure AD Connect", que vos utilisateurs sont dans différents pays et que vous utilisez l'audioconférence dans Teams, il semblerait logique que vos utilisateurs se voient attribuer un numéro "locale", plutôt qu'un numéro "Français".

 

Pour ce faire il est important de définir le lieu d'utilisation (Usage Location) dans Azure Active Directory, il est donc important de remplir l'attribut "Country" dans votre Active Directory OnPremise.

La construction est basé sur deux lettres, vous pourrez trouver la correspondance des pays et des lettres dans le lien ci-dessous:

Attention : Vous devrez utiliser les deux lettres en majuscule de la colonne "Language Culture Name"

https://docs.microsoft.com/en-us/previous-versions/commerce-server/ee825488(v=cs.20)?redirectedfrom=MSDN

 

Vous pouvez fixer cet attribut à l'aide de Powershell via la commande ci-dessous (en prenant soin de remplacer "Mathieu" par le nom de votre utilisateur) :

Get-ADUser Mathieu | Set-ADUser -Replace @{‘Country’=”US”}

Ou alors en important un fichier csv pour une modification en masse, exemple de code ci-dessous.

# Import Csv and Set PreferredLanguage Attribute for each user
Import-csv C:\Temp\ToFixed.csv | foreach {
    $SamAccountName = $_.SamAccountName
    $Attribute = $_.Attribute
    Try {
        # Modifying PreferredLanguage Attribute
        Set-ADUser -Identity $SamAccountName -Replace @{‘Country’=$Attribute} -ErrorAction Stop 
        }
    Catch {
        $SamAccountName | Add-Content C:\temp\ErrorUsageLocation.txt
        }
    # Release
    $SamAccountName = $null
    $Attribute = $null
    }