PI Services

Le blog des collaborateurs de PI Services

[Powershell] Ecrire dans un Excel distant.

Exécuter Excel sur un poste Distant.

Quel est l'interêt ?

En dehors du fait qu'installer Excel sur un serveur n'est pas forcément une bonne pratique, dans mon cas cela me permet d'automatiser à 100% mes scripts Powershell, ces derniers fournissent des rapports détaillés à mes interlocuteurs, sans que je n'ai besoin de faire quelques modifications que ce soit (contrairement à un CSV).
Ainsi les scripts tournent de nuit et permettent à chacun de trouver son rapport le matin en arrivant, et ce même pendant mes vacances.

Comment ça marche

Pour exécuter Excel sur un poste distant via Powershell il faudra cumuler 2 paramètres.

  1. Activer CredSSP pour la délégation des identifiants.
  2. Autoriser le compte d'exécution à se servir de l'application Excel à Distance.

Voyons comment réaliser cela.

1- Activer CredSSP

J'ai déjà traité le sujet ICI.

2- Gestion Excel Distant

Attention la modification que nous allons réaliser n'est pas sans conséquence, comme nous pourrons le voir après, l'application Excel devient vraiment limitée pour tout autre utilisateur que celui qui sera déclaré.

Pour pouvoir autoriser le compte d'exécution à se servir de l'application Excel à distance, nous aurons besoin de la MMC.

Sur la machine qui traitera le fichier Excel :

  • Ouvrir la MMC > Ajouter ou supprimer des composants logiciels enfichables > Services de composants.

  • Développer Services de composants > Ordinateurs > Poste de travail > Configuration DCOM

  • Sur "Microsoft Excel Application" faites clic droit "propriétés", dans la fenêtre sélectionnez l'onglet "Identité"

  • Cochez la case "Cet utilisateur" puis faites "parcourir", sélectionnez "Tout l'annuaire" et enfin recherchez le compte d'exécution du script.

  • Entrez le mot de passe de votre compte d'exécution et faites "Appliquer".

Maintenant que le paramètre est fixé seul le compte définit est "autorisé" à se servir de l'application Microsoft Excel, l'expérience pour tout autre utilisateur est vraiment dégradé (pas d'impression, de sauvegarde, message d'erreur...); par conséquent si vous souhaitez pouvoir continuer à utiliser Excel, je vous invite à repasser sur "L'utilisateur exécutant" et n'activer la fonction que lors de l'exécution du script (pour ma part je ne l'active sur mon poste que la nuit).

Exemple de message d'erreur.

 

Annexe

En annexe un petit article sur la gestion d'Excel en Powershell :

https://blog.piservices.fr/post/2013/03/27/Powershell-Creation-de28099objets-personnalises

 

Supervision – SQL – 3 scripts pour la supervision de Always On

 

Dans le cadre d’un portage de règle de supervision, les trois scripts ci-dessous ont été crées pour remonter l’état de:

- Un Availability Group

- Un Availability Replica

- Un Database Replica

 

En pratique ils utilisent a peu près la même requête SQL. Ils contiennent des éléments propre a l’api scom mais peuvent bien sur être adapté pour être utilisé indépendamment.

Ci-dessous les 3 scripts et l’exemple du code du premier, Check_SQL_AO_AvailabilityGroupStatus_Query_Version.ps1.

 

 

#######################################################################################
#         
#
         
#          Script: SQLAlwaysOnAvailGroupStatus.ps1

#          Purpose: Shows AlwaysOn Availability Group Status
#         
#          Parameters:

#           $DBServer: ShortName of DB Server
#          $InstanceFullName: Name of SQL Instance
#          $AvailGroupName: Availability Group Name
#         
#

########################################################################################

param ( 
           
$Arguments,

           
[string]$DBServer,
           
[string]$InstanceFullName,
       
[string]$AvailGroupName
           
                          
           
)


# Create local variables from override value
.([Scriptblock]::Create($Arguments))

$Scriptname = "SQLAlwaysOnAvailabilityReplicaStatus.ps1"


# Name of Instance

$ServerInstance = $InstanceFullName.Split('\')[1]



    #Determine TcpPort Used for Instance
    try
    {
    $TcpPort = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.$ServerInstance\MSSQLServer\SuperSocketNetLib\Tcp\IpAll" | select TcpPort -exp tcpport
    }
    catch
    {
    $Message = "Error during Retrieve of TCP Port Used - Check the execution of the script"
    write-host -f Yellow $Message
    $PropertyBag.AddValue("State","WARNING")
    $PropertyBag.AddValue("Message",$Message)
    $PropertyBag.AddValue("ReplicaName",$AvailabilityReplicaName)
    $PropertyBag
    Exit 1
    }





# Scom Object and Property Bag
$api = New-Object -comObject “MOM.ScriptAPI” 
$PropertyBag = $api.CreatePropertyBag()


# Function  GetAODBRepStatus
Function GetAOAvailGroupStatus
                        {
                        param(
                                    [string]$TargetComputer=$DBServer,
                                    $global:Source = "$DBServer\$ServerInstance",
                                    [string]$sqlCommand =
                                            $("
                                            SELECT
                                           
                                            AO_AG.name as AvailGroupName
                                            ,HADR_AO_AGS.synchronization_health as AvailGroup_SyncHealth
                                            ,HADR_AO_AGS.synchronization_health_desc as AvailGroup_SyncHealth_Desc
                                            --,SYSDB.name as DBName
                                           
                                            --,AO_AVREP.replica_server_name
                                            --,HADR_AVAIL_REP_STATE.synchronization_health as AvailReplica_SyncHealth
                                           
                                            --,HADR_AVAIL_REP_STATE.synchronization_health_desc as AvailReplica_SyncHealth_Desc

                                            --,HADR_DB_REP_STATE.synchronization_state as DBReplica_SyncState
                                            --,HADR_DB_REP_STATE.synchronization_state_desc as DBReplica_SyncState_Desc
                                            --,HADR_DB_REP_STATE.synchronization_health_desc as DBReplica_SyncHealth_Desc

                                           
                                            FROM
                                            [sys].[availability_databases_cluster] AO_DB_CLUS
                                            --INNER JOIN sys.databases SYSDB on CAST(SYSDB.group_database_id AS VARCHAR(50)) =  CAST(AO_DB_CLUS.group_database_id AS VARCHAR(50))
                                            INNER JOIN sys.dm_hadr_database_replica_states HADR_DB_REP_STATE on CAST(HADR_DB_REP_STATE.group_database_id AS VARCHAR(50)) = CAST(AO_DB_CLUS.group_database_id AS VARCHAR(50))
                                            --INNER JOIN sys.dm_hadr_availability_replica_states HADR_AVAIL_REP_STATE on HADR_AVAIL_REP_STATE.group_id = HADR_DB_REP_STATE.group_id
                                            INNER JOIN sys.availability_groups AO_AG on AO_AG.group_id = HADR_DB_REP_STATE.group_id
                                            INNER JOIN sys.dm_hadr_availability_group_states HADR_AO_AGS on HADR_AO_AGS.group_id = AO_AG.group_id
                                            --INNER JOIN [sys].[availability_replicas] AO_AVREP on AO_AVREP.replica_id = HADR_DB_REP_STATE.replica_id
                                            WHERE AO_AG.name = '
$AvailGroupName
'
                                           
                                            GROUP BY
                                            AO_AG.name
                                            ,HADR_AO_AGS.synchronization_health
                                            ,HADR_AO_AGS.synchronization_health_desc
                                            "
                                            )
                               )

                               Try
                               {

                                                                                           
                                               

                                            $global:connectionString = "Data Source=$Source,$TcpPort;" +
                                            "Integrated Security=SSPI; " +
                                            "Initial Catalog=master"

                                           
                                            $connection = new-object system.data.SqlClient.SQLConnection($connectionString)
                                           
                               
                                                               
                                            $connection.Open()
                                                                                 
                                                                              


                                            $command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)

                                }
                                catch
                                {
                                write-host -F Red $("Error during sql connection - check the credentials used").ToUpper()
                                #exit 1
                                }

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

                                $connection.Close()
                                $Set.Tables

                               

                              }




# Execute function and get data
try
{
[array]$AvailGroup = GetAOAvailGroupStatus
}
catch
{
$Message = "WARNING - Error during Connection to master database or execution of sql query"
write-host -F Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("connectstring",$connectionString)
$PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}



if (!($AvailGroup))
{
$Message = "WARNING - Error - No Availability Group have been found"
write-host -F Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("AvailGroupName","no_data")
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}



 

try
{
$AvailGroupState = $AvailGroup  | select AvailGroup_SyncHealth_Desc -ExpandProperty AvailGroup_SyncHealth_Desc
}
catch
{
$Message = "Error during Retrieve of Availability Group State"
Write-Host -ForegroundColor Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}




"AVAILABILITY GROUP: $AvailGroupName"



If ($AvailGroupState -eq "Healthy")

        {
        $Message = "OK - Status of $AvailGroupName Availability Group is Healthy"
        write-host -f Green $Message
        $PropertyBag.AddValue("State","OK")
        $PropertyBag.AddValue("Message",$Message)
        $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","Healthy")
        $PropertyBag
        Exit 0
        }
       


ElseIf ($AvailGroupState -eq "Error")

   
        {
        $Message = "CRITICAL - Status of $AvailGroupName Availability Group is Error"
        write-host -f Red $Message
        $PropertyBag.AddValue("State","CRITICAL")
        $PropertyBag.AddValue("Message",$Message)
      $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","Error")
        $PropertyBag
        Exit 0
        }
       
Else   {
        $Message = "WARNING - Status of $AvailGroupName Availability Group cannot be determined"
        write-host -f yellow $Message
        $PropertyBag.AddValue("State","WARNING")
        $PropertyBag.AddValue("Message",$Message)
      $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","no_data")
        $PropertyBag
        Exit 1
        }
 
 
########################################################################################

Supervision – Script de corrélation a la seconde

 

Une demande m’a été faite récemment pour la détection de l’occurrence de deux events distinct a la même seconde, ce cas particulier traduisant un problème de sécurité spécifique.

Indépendamment de l’objectif final, il s’agit d’un cas intéressant auquel le script ci-dessous a répondu. Il contient des éléments propre a l’api scom mais peux bien sur être adapté pour être utilisé indépendamment.

 

 

  ##############################################################
### SCRIPT TO DETECT SPECIFIC TWO EVENTS OCCURING AT SAME TIME #####
##############################################################

# PARAMETERS:
### $EventLog: Event Log to look in
### $EventSource: Event Source to search for 

### $FirstEventId: First event to correlate

### $SecondEventId: second event to correlate

### $LastMinutes: Last Time Window to search in
### $DayOfWeekToExclude: Day Of Week To Exclude (Example: "('Saturday','Sunday')" )




param(
$Arguments,
$EventLog,
$EventSource,
$FirstEventId,
$SecondEventId,
$LastMinutes,
$DayOfWeekToExclude
)

$ScriptName = "CorrelateTwoSpecEvent.ps1"

#FUNCTIONS


#Check for the existence of an event source with script name in operation manager eventlog to log some events 
        
Function NewEventSource
 
        
{
 
        
if(!(Test-Path "HKLM:\SYSTEM\CurrentControlSet\services\eventlog\Operations Manager\$ScriptName"))
 
        
{
 
        
New-EventLog -LogName "Operations Manager" -Source $ScriptName
 
        
}

        
} 

#END FUNCTIONS


#Log of script execution 
NewEventSource
 
write-eventlog -logname "Operations Manager" -Source $ScriptName -EventID 1000 -Message "Execution du script $ScriptName" -EntryType Information
 


# Create local variables from override value

.([Scriptblock]::Create($Arguments))

# Determine the moment in the week
if ((Get-date).DayOfWeek -in $DayOfWeekToExclude)
{
# If the day is in $DayOfWeekToExclude -> NO ACTION - END OF SCRIPT
Write-Host "$((Get-date).DayOfWeek) : NO ACTION - END OF SCRIPT"
Exit 0

}

# Create the Scom property bag
$ScomAPI = New-Object -comObject "MOM.ScriptAPI"
$PropertyBag = $ScomAPI.CreatePropertyBag()



$Message =     "SEARCH CRITERIAS: Log: $EventLog - Source: $EventSource - EventId: $FirstEventId or $SecondEventId `n"
$Message


try
{
New-Variable -Name "$($FirstEventId)_Events" -Force -Value $(Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{logname=$EventLog;ProviderName=$EventSource;id=$FirstEventId;StartTime=$(get-date).AddMinutes(-$LastMinutes)})
New-Variable -Name "$($SecondEventId)_Events" -Force -Value $(Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{logname=$EventLog;ProviderName=$EventSource;id=$SecondEventId;StartTime=$(get-date).AddMinutes(-$LastMinutes)})
}
catch
{
$Message = "Error during retrieve of events in the $ScriptName script"
$Message

NewEventSource
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1001 -EntryType Warning -Message "$Message"
Exit 1

}

#If no one of the two events id have occurence no need to continue
if (!$(Get-Variable "$($FirstEventId)_Events").Value -and !$(Get-Variable "$($SecondEventId)_Events").Value)
   
{
   
$Message =  "No one of the two events id have occurences in last $LastMinutes minutes - END OF SCRIPT"
   
$Message

   
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1002 -EntryType Information -Message "$Message"

   
Exit 0

   
}


#If Only one of the two events id have occurences no need to continue
if (!$(Get-Variable "$($FirstEventId)_Events").Value -or !$(Get-Variable "$($SecondEventId)_Events").Value)
   
{
   
$Message = "Only one of the two events id have occurences - END OF SCRIPT"
   
$Message

   
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1003 -EntryType Information -Message $Message

   
Exit 0
   
}



$Message = "$($(Get-Variable "$($FirstEventId)_Events").Value.count) occurence of event $FirstEventId and $($(Get-Variable "$($SecondEventId)_Events").Value.count) occurence of event $SecondEventId in the last $LastMinutes minutes"
$Message +=
"`nSTART OF COMPARAISON...`n"
#$Message



#Compare DateTimes at second level
try
{
$CompareResult = Compare-Object -ReferenceObject $(Get-Variable -Name "$($FirstEventId)_Events").Value.timecreated.second -DifferenceObject $(Get-Variable -Name "$($SecondEventId)_Events").Value.timecreated.second -ExcludeDifferent -IncludeEqual -Verbose
}
catch
{
$Message += "Error during comparaison of Date Creation"
$Message

NewEventSource
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1004 -EntryType Warning -Message $Message
Exit 1
}

#If $CompareResult is null, Events have not occureat the same time
If (!($CompareResult))
   
{
   
$Message += "Events $FirstEventId and $SecondEventId have not occured at the same second - No correlation"
   
$Message

   
NewEventSource   
   
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1004 -EntryType Information -Message $Message
   
   
exit 0

   
}

Else
   
{
   
NewEventSource
   
#
   
$Message += "EVENT $FirstEventId and $SecondEventId have occured at same second $($CompareResult.count) times `n"
   
$Message +=
"`nEVENTS OF LAST $LastMinutes MINUTES:`n"
       
   
$Message += $(Get-Variable "$($FirstEventId)_Events").value | foreach {$_} | Out-String
 
   
$Message += $(Get-Variable "$($SecondEventId)_Events").value | foreach {$_} | Out-String

   
$Message
   
Write-EventLog -LogName "operations manager" -Source $ScriptName -EventId 1005 -EntryType Information -Message $Message
   
$PropertyBag.AddValue("State","CRITICAL")
   
$PropertyBag.AddValue("Message",$Message)
   
   
$PropertyBag
   
}


 
 

[Powershell] CredSSP : Credential Security Service Provider.

CredSSP qu'est ce que c'est ?

CredSSP : Credential Security Service Provider.

A quoi cela sert-il ?

A déléguer des identifiants pour une session distante, plus précisément, CredSSP vous permet de fournir une authentification de bout en bout au travers de plusieurs sessions distantes.

Exemple :

Depuis un Serveur A un script Powershell ouvre une session distante sur un Serveur B, ce dernier traite ses instructions puis ouvre une session distante pour requêter un Serveur C et y déposer ses résultats sur un partage.

Lors de l'ouverture de la session distante entre A et B Windows utilise les identifiants fournis, mais lors de l'ouverture de la session distante depuis B vers C, Windows considère qu'il s'agit d'une usurpation Kerberos (car par défaut WinRm n'autorise pas la délégation des identifiants).

Afin de palier  cela que ce soit pour un "Invoke-Command" ou un "New-PSSession" vous devrez ajouter "-Authentication CredSSP"; cet ajout vous permettra de déléguer les identifiants pour la seconde session distante.

Mise en oeuvre :

Et non, on ne peut pas utiliser l'option CredSSP sans prérequis, voici ceux à mettre en place.

Sur le Serveur A on va activer CredSSP "Client" via la commande suivante :

Enable-WSManCredSSP -Role Client -DelegateComputer ServerB.mondomaine.com -Force

On peut vérifier que cela à bien fonctionné à l'aide de la commande suivante :

Get-WSManCredSSP

Puis sur le Server B on va activer CredSSP "Server" via la commande suivante :

Enable-WSManCredSSP -Role Server -Force

vérifions.

Et voilà nous devrions donc pouvoir exécuter la commande initiale depuis le serveur A, vérifions cela sans oublier d'ajouter "-Authentication CredSSP".

Enter-PSSession -ComputerName lab01-wsus1.LAB.ORG -Credential Lab\Adminmad -Authentication Credssp

Jusque la tout fonctionne, essayons donc maintenant de requêter vers le serveur C.

Invoke-Command -ComputerName Lab01-wsus2.lab.org -ScriptBlock {Get-WindowsFeature | Where-Object {$_.Name -like "UpdateServices"}}

C'est bien fonctionnel, bien entendu comme ce changement est lié à la sécurité, il est préconisé de désactiver cette option après usage.

On peut utiliser les commandes :

Disable-WSManCredSSP –Role Client # Sur le serveur A
Disable-WSManCredSSP –Role Server # Sur le serveur B

Ou directement depuis le serveur A :

Invoke-Command –ComputerName ServerB –ScriptBlock { Disable-WSManCredSSP –Role Server }
Disable-WSManCredSSP –Role Client

 

Annexes :

Vous pouvez aussi le faire sur plusieurs Serveurs : 

Enable-WSManCredSSP -Role "Client" -DelegateComputer "ServerB.mondomaine.com", "ServerC.mondomaine.com", "ServerD.mondomaine.com" -Force

Ou sur toutes les machines du domaine :

Enable-WSManCredSSP -Role "Client" -DelegateComputer "*.mondomaine.com" -Force

 Pour plus d'informations:

https://docs.microsoft.com/en-us/powershell/module/microsoft.wsman.management/enable-wsmancredssp?view=powershell-6 

https://msdn.microsoft.com/en-us/library/windows/desktop/bb931352(v=vs.85).aspx

Powershell : Limite de requête Active Directory via ADWS

Problème

Lors de l'utilisation d'un script qui génère plusieurs requêtes depuis plusieurs serveurs en simultané afin d'alimenter une banque de données, je me suis heurté au message d'erreur suivant :

get-adcomputer : A connection to the directory on which to process the request was unavailable. This is likely a 
transient condition.
At C:\temp\Fusion.ps1:97 char:1
+ get-adcomputer -Filter {DNSHostName -eq $FullName} -Properties OperatingSystem | Select-Object -Property N ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ADComputer], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADComputer

Explication

Cette erreur est liée aux valeurs par défaut du service "Active Directory Web Services" définies dans le fichier de configuration sur les DC sous : 

C:\Windows\ADWS\Microsoft.ActiveDirectory.WebServices.exe.config

En effet si vous éditez le fichier de configuration vous pourrez voir que par défaut le maximum de connexions LDAP par utilisateur est de 5.

<add key="MaxConnectionsPerUser" value="5" />

Ce qui signifie que je ne peux exécuter plus de 5 connexions en simultanée via Active Directory Web Services avec les même identifiants.

Il est possible de modifier cette valeur, mais cette action n'est pas recommandée car elle peut avoir un impact sur les performances de l'AD.

Si toutefois vous souhaitez la modifier, certaines précautions sont à prendre; il est clairement précisé dans le fichier de configuration que la valeur de "MaxConnectionsPerUser" ne peut pas être plus grande que la valeur de "MaxPoolConnections", il faudra donc aussi prendre en compte la valeur suivante :

<add key="MaxPoolConnections" value="10" />

Cette dernière spécifie que le nombre maximal de connexions LDAP, pour chaque instance de service d'annuaire prise en charge par le service ADWS s'exécutant sur un serveur.

Il peut être aussi intéressant de regarder la valeur de la clé ci-dessous : 

<add key="MaxPercentageReservedConnections" value="50" />

Celle-ci permet, de spécifier le pourcentage maximal de connexions LDAP pouvant être utilisées pour effectuer des requêtes pour chaque instance de service d'annuaire prise en charge par le service ADWS sur un serveur.

 

Attention :

Même s'il est possible de modifier les paramètres ci-dessus, Microsoft ne le préconise pas (cf: note ci-dessous) :

Plusieurs paramètres de configuration du service ADWS affectent la limitation de la bande passante sur un serveur Windows Server 2008 R2 sur lequel le service ADWS est en cours d'exécution.
Nous recommandons aux administrateurs de modifier les valeurs par défaut des seuls paramètres suivants:
MaxConcurrentCalls, MaxConcurrentSessions, MaxReceivedMessageSize et MaxStringContentLength.

 

Compléments d'informations:

https://technet.microsoft.com/en-us/library/373e68b3-abfc-4da4-ae89-72a15cfc7543

SCOM – Script Exemple de rapport Cmdline pour un monitor

Le script ci-dessous montre comment facilement sortir de petit rapport en ligne de commande de l’état d’un monitor pour tout les agents.

Dans cet exemple on affiche l’état du rollup monitor de l’espace disque (FreeSpaceMonitorRollup) et l’information de maintenance mode.

 

Function GetLogicDiskFreeSpState 
     
{

     
param(
     
[string]$dataSource = "SQLSERV1\OPSMGR",
     
[string]$database = "OperationsManager",
     
[string]$UnitMonitor = "Microsoft.Windows.Server.%.LogicalDisk.FreeSpaceMonitorRollup",
     
[string]$TypeName = "Microsoft.Windows.Server.%.LogicalDisk",
     
[string]$sqlCommand = 
     
$(
"
                                                             
      /* QUERY THAT GET STATE OF SPECIFIC MONITOR STATE FOR ALL COMPUTERS */
      Use $database
                                                                                    
      DECLARE @UnitMonitor VARCHAR(100)
      DECLARE @TypeName VARCHAR(100)
                                           
                       
      SET @UnitMonitor = '%'+'$UnitMonitor'+'%'
      SET @TypeName = '$TypeName'
                                          
      PRINT 'MONITOR: ' + @UnitMonitor
      PRINT 'CRITERIAS:'
                                           
     ;
     WITH
                                                       
     MAININFO (Monitor,Computer,Disk,HealthState, LastModified,IsAvailable,InMaintenanceMode)
     AS
     (                                           
      SELECT
      MV.Name as Monitor
     ,MEGV.path as Computer
     ,MEGV.Name as Disk
     ,HealthState = CASE WHEN InMaintenanceMode = '0' OR InMaintenanceMode is null  
        THEN     
            CASE MEGV.IsAvailable
                  WHEN '0' THEN 'KO - STATE IS NOT AVAILABLE' -- THIS MEAN THAT THE STATE IS GRAYED IN SCOM CONSOLE DESPITE OF THE OBJECT IS NOT IN MAINTENANCE MODE (AGENT FUNCTIONNAL PROBLEM)
                  WHEN '1' THEN
                    CASE SV.[HealthState]
                        WHEN '0' THEN 'Not Monitored'
                        WHEN '1' THEN 'OK'
                        WHEN '2' THEN 'Warning'
                        WHEN '3' THEN 'Critical'
                    END
                                                   
                END                   
                                                       
            WHEN InMaintenanceMode = '1'
        THEN
            CASE MEGV.IsAvailable
            WHEN '0' THEN 'KO - STATE IS NOT AVAILABLE' -- THIS MEAN THAT THE STATE IS GRAYED IN SCOM CONSOLE DESPITE OF THE OBJECT IS IN MAINTENANCE MODE (AGENT FUNCTIONNAL PROBLEM)
            WHEN '1' THEN
                CASE SV.[HealthState]
                WHEN '0' THEN 'In Maintenance Mode'
                WHEN '1' THEN 'OK'
                WHEN '2' THEN 'Warning'
                WHEN '3' THEN 'Critical'
                END
                                                       
            END                                                                                           
        END

    ,SV.[LastModified] as LastModified
    ,MEGV.IsAvailable
    ,MEGV.InMaintenanceMode
    FROM [OperationsManager].[dbo].[StateView] SV
    INNER JOIN [dbo].[ManagedEntityGenericView] MEGV on SV.BaseManagedEntityId = MEGV.BaseManagedEntityId
    INNER JOIN [dbo].[MonitorView] MV on SV.MonitorId = MV.id
    INNER JOIN [dbo].[ManagedTypeView] MTV on MEGV.MonitoringClassId = MTV.Id
    WHERE MV.Name like @UnitMonitor
    AND MTV.Name like @TypeName
        )
    SELECT
        
    MAININFO.Monitor
   ,MAININFO.Computer
   ,MAININFO.Disk
   ,MAININFO.HealthState
   ,MAININFO.LastModified
   ,MAININFO.IsAvailable
   ,MAININFO.InMaintenanceMode
    FROM MAININFO
            
   ORDER BY computer,Disk
  "
    
)

 
)

 
$connectionString = "Data Source=$dataSource; " +
  "Integrated Security=SSPI; "
+
  "Initial Catalog=$database"

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

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

 
$connection.Close()

 
#Display Time of Query
"`n"
 
write-host "Query Date: $(get-date -Format F)" -NoNewline
"`n"

#Display Criterias
Write-Host "STATE OF MONITOR `"$UnitMonitor`":"
"`n"
#Display Nb of rows

write-host Nb Of Object: $($dataset.Tables.defaultview.Count)
"`n"

$dataSet.Tables
}



try
{
GetLogicDiskFreeSpState | ft -AutoSize
}
catch
{
write-host -F Red "ERROR DURING EXECUTION OF GetLogicDiskFreeSpState FUNCTION - CHECK THAT YOU ARE LOGGED WITH A RIGHT ACCOUNT OR THAT THE SQL QUERY IS CORRECT"
}

Gestion de WSUS en Powershell - Partie 4

1- Décliner des KB

Voici comment décliner toutes les KB de sécurité remplacées (Superseded).

# Decline Action
$KBState = $Wsus.GetStatus()
$DeclinedBefore = $KBState.DeclinedUpdateCount
$DeclineSupersed = $Wsus.GetUpdates() | Where-Object {($_.UpdateClassificationTitle -eq "Security Updates") -and ($_.IsSuperseded -eq $True)}
$DeclineSupersed | ForEach-Object -Process {$_.Decline()}
$DeclinedAfter = $KBState.DeclinedUpdateCount

Write-Host "Before we had $DeclinedBefore KB declined, and now  $DeclinedAfter"

2 - Approuver des KB

 Pour approuver des KB de sécurité non approuvées et non remplacées.

# Approve Action
$AproveKB = $Wsus.GetUpdates() | Where-Object {($_.UpdateClassificationTitle -eq "Security Updates") -and ($_.IsSuperseded -eq $false) -and ($_.IsApproved -eq $false) -and ($_.State -ne "NotNeeded")}
$MyTarget = $wsus.GetComputerTargetGroups() | Where-Object {$_.Name -eq "TEST-Workstations"}
$AproveKB[0].ApproveForOptionalInstall($MyTarget)

Attention dans l'exemple ci-dessus nous approuvons les KB "nécessaires" pour un groupe appelé "TEST-Workstations", pensez à modifier la variable "$MyTraget" avec le nom de votre ou vos groupes.

Exemple:

# Approve Action
$ApprovedKB = $Wsus.GetUpdates() | Where-Object {($_.UpdateClassificationTitle -eq "Security Updates") -and ($_.IsSuperseded -eq $false) -and ($_.IsApproved -eq $false) -and ($_.State -ne "NotNeeded")}
$MyTarget = $wsus.GetComputerTargetGroups() | Where-Object {$_.Name -eq "Prod_Servers"}
$AproveKB[0].ApproveForOptionalInstall($MyTarget)

 

Active Directory : Réparer le Secure Channel

Bonjour à tous,

Aujourd'hui nous allons aborder la problématique du Secure Channel (canal sécurisé) dans une infrastructure Active Directory.

Qu'est ce que le Secure Channel ?

Le Secure Channel ou Canal Sécurisé est un élément vital dans une infrastructure Active Directory. En effet, toute communication entre une station de travail et un contrôleur de domaine AD doit passer via un canal sécurisé.

Il est à noter que le canal sécurisé est également utilisé pour les communications entre deux contrôleurs de domaine.

Quand le canal sécurisé est cassé, toutes les opérations Active Directory liées au canal sont en échec (Tickets Kerberos, stratégies de groupes, ...).

Quand le Secure Channel est cassé

Le signe le plus flagrant d'un problème de canal sécurisé entre un poste et un contrôleur de domaine est le message suivant au démarrage du poste : "The trust relationship between this workstation and the primary domain failed".

La plupart du temps, ceci est du au fait que le mot de passe en usage pour établir le Secure Channel sur le poste concerné est différent du mot de passe du compte ordinateur stocké dans l'AD.

Réparer le Secure Channel

Habituellement, l'étape de réparation passe par les étapes suivantes :

  1. Sortie du poste du domaine pour un retour en Workgroup
  2. Réinitialisation du compte ordinateur correspondant dans l'Active Directory
  3. Réintégration du poste dans le domaine

L'ancienne méthode

Une méthode, plus propre, et qui vous épargnera un redémarrage consiste à réparer le canal sécurisé directement avec une invite de commande.

La commande à utiliser est netdom resetpwd /s:domaincontroller /ud:domain\User /pd:*

La nouvelle méthode

Avec l'avènement de Powershell, une nouvelle commande a fait son apparition : Test-ComputerSecureChannel.

Si la commande renvoie True, c'est que le Secure Channel est fonctionnel entre le poste et le contrôleur de domaine concerné.

Si elle renvoie False, c'est que le Secure Channel n'est plus fonctionnel entre le poste et le contrôleur de domaine concerné.

Il faut l'utiliser avec l'option Repair afin de réparer le Secure Channel.

Le retour de la commande, True ou False, indique le succès ou non de la réparation du Canal Sécurisé.

A noter qu'à partir de Powershell v4.0, l'option Credential permet de préciser les identifiants à utiliser pour réparer le canal sécurisé.

Version Windows 7 / Windows Server 2008 R2 :

Version Windows 8 / Windows Server 2012 R2 :

Gestion de WSUS en Powershell - Partie 3

Dans cette partie nous allons voir comment utiliser la fonction Cleanup via Powershell

1 - Les Variables

La fonction "Cleanup" permet de nettoyer la base du serveur WSUS.

Il est possible de supprimer :

  • Les Superseded Updates  ou mises à jour remplacées en Français
  • Les Expired Updates ou mises à jour expirées en Français
  • Les Obsolete Updates ou mises à jour obsolètes en Français
  • Les Compress Updates ou mises à jour inutiles en Français
  • Les Obsolete Computers ou les ordinateurs obsolètes en Français
  • Les Unneeded ContentFiles ou Fichiers de mise jour inutiles en Français

Dans l'exemple ci-dessous les variables seront par défaut à "$False" il suffit de mettre "$True" pour valider la fonction.

    # Variables de Cleanup: 
# Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an aproved update. 
[Boolean]$SupersededUpdates = $false 
# Decline updates that aren't approved and have been expired my Microsoft. 
[Boolean]$ExpiredUpdates = $false 
# Delete updates that are expired and have not been approved for 30 days or more. 
[Boolean]$ObsoleteUpdates = $false 
# Delete older update revisions that have not been approved for 30 days or more. 
[Boolean]$CompressUpdates = $false 
# Delete computers that have not contacted the server in 30 days or more. 
[Boolean]$ObsoleteComputers = $True 
# Delete update files that aren't needed by updates or downstream servers. 
[Boolean]$UnneededContentFiles = $false 

 

2 - La commande

Une fois les variables définies, il faut indiquer le "Cleanup Scope" qui permet d'établir quels paramètres seront nettoyer, pour cela nous utiliserons la commande suivante :

$CleanupScope = New-Object Microsoft.UpdateServices.Administration.CleanupScope($supersededUpdates,$expiredUpdates,$obsoleteUpdates,$compressUpdates,$obsoleteComputers,$unneededContentFiles)

Une fois le "Cleanup Scope" définit, il ne reste plus qu'a exécuter la commande de nettoyage ci-dessous : 

($Wsus.GetCleanupManager()).PerformCleanup($CleanupScope)

Ou

$Cleanup = $Wsus.GetCleanupManager()
$Cleanup.PerformCleanup($CleanupScope)

 

3 - Bonus

Si vous possédez plusieurs serveurs WSUS, il est possible d'exécuter ce script (dans cet exemple nous ciblons uniquement les Ordinateurs obsolètes, remplacez les "$False" par "$True" pour valider les autres paramètres) :

# Script de Cleanup

$LogCatch = "$env:USERPROFILE\Desktop\LogCatch.txt"

# Détection des WSUS
Get-ADComputer -Filter { (Name -like "*WSUS*") -and (Enabled -eq $true)} | Select-Object -Property DNSHostName | Sort-Object -Property DNSHostName | ForEach-Object {
    $DNSHostName = $_."DNSHostName"
    
#region - Connexion au WSUS
        # Varibles de connexions
            $WsusServer = $DNSHostName
            $WsusPort = "8530"
        # Valeur max de prise en compte d'une machine (ici 30 jours sans connexion au serveur WSUS)
            $thirtydaysago = (get-date).adddays(-30)
            $DaysComputerStale = "30"

        #region - Ouverture de la connexion au serveur 
        $ErrorActionPreference = 'SilentlyContinue'
        Try {
            [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
            $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$false,$WsusPort)
            $Wsus.Name
            $Log = $Wsus.Name
            }
        Catch {
            Write-Warning "$($WsusServer)<$($WsusPort)>: $($_)" | Add-Content -Path $LogCatch
            $Connection = "Failed"
            $finalWorkSheet.Cells.Item($FinalExcelRow,9) = $Connection
            }
            If ($Log -eq $null){
                Try {
                    $WsusPort2 = "80"
                    [void][reflection.assembly]::loadwithpartialname("microsoft.updateservices.administration")
                    $Wsus = [microsoft.updateservices.administration.adminproxy]::getupdateserver($WsusServer,$false,$WsusPort2)
                    $Wsus.Name
                    }
                Catch {
            Write-Warning "$($WsusServer)<$($WsusPort2)>: $($_)" | Add-Content -Path $LogCatch
                        }
                }
        $ErrorActionPreference = 'SilentlyContinue'
        #endregion - Ouverture de la connexion au serveur
#endregion - Connexion au WSUS

#region - Cleanup
    # Variables de Cleanup: 
    # Decline updates that have not been approved for 30 days or more, are not currently needed by any clients, and are superseded by an aproved update. 
    [Boolean]$supersededUpdates = $false 
    # Decline updates that aren't approved and have been expired my Microsoft. 
    [Boolean]$expiredUpdates = $false 
    # Delete updates that are expired and have not been approved for 30 days or more. 
    [Boolean]$obsoleteUpdates = $false 
    # Delete older update revisions that have not been approved for 30 days or more. 
    [Boolean]$compressUpdates = $false 
    # Delete computers that have not contacted the server in 30 days or more. 
    [Boolean]$obsoleteComputers = $True 
    # Delete update files that aren't needed by updates or downstream servers. 
    [Boolean]$unneededContentFiles = $false 

    $CleanupScope = New-Object Microsoft.UpdateServices.Administration.CleanupScope($supersededUpdates,$expiredUpdates,$obsoleteUpdates,$compressUpdates,$obsoleteComputers,$unneededContentFiles) 

    ($Wsus.GetCleanupManager()).PerformCleanup($CleanupScope)

#endregion - Cleanup

#region - Release des Variables
$Name = $null
$WsusPort = $null
$Wsus = $null
$Log = $null
#endregion - Release des Variables
}

La nomenclature de mes serveurs comporte "WSUS" dans le nom, je passe donc par un "Get-Adcomputer", mais vous pouvez très bien remplacer cela par un "Import-CSV".

Gestion de WSUS en Powershell - Partie 2

Dans l'article précédent nous avons vu comment nous connecter au serveur WSUS en Powershell, voyons maintenant ce que l'on peut récupérer informations.

1 - Etat de la configuration du serveur WSUS

Pour obtenir la configuration du serveur WSUS faites :

$Wsus.GetConfiguration()

Cette commande vous permet d'obtenir l'ensemble de la configuration du serveur.

Nous pouvons par exemple définir cette commande comme variable afin de pouvoir récupérer des informations de manière plus précise (car la commande retourne beaucoup d'informations).

Dans notre exemple nous allons récupérer les informations ci-dessous (pour information mon infrastructure possède un UpstreamServer) :

  • Le nom de l'UpstreamServer
  • Le port de connexion à l'UpstreamServer
  • Le serveur est il autorisé à synchroniser avec Windows Update
  • Le chemin d'accès au stockage des mise à jours
  • L'emplacement du fichier de log

J'ai variabilisé les requêtes car elles me servent plusieurs fois dans le script.

$Config = $Wsus.GetConfiguration()
$UpstreamServer = $Config.UpstreamWsusServerName
$UpstreamPort = $Config.UpstreamWsusServerPortNumber
$SyncFromMU = $Config.SyncFromMicrosoftUpdate
$StoragePath = $config.LocalContentCachePath
$LogPath = $Config.LogFilePath

$Config | Select-Object -Property UpstreamWsusServerName,UpstreamWsusServerPortNumber,SyncFromMicrosoftUpdate,LocalContentCachePath,LogFilePath

Si le serveur WSUS n'est pas le serveur en Amont (Upstream Server), donc en aval (Downstream Server) il est possible de connaitre ses paramètres via la commande :

$Wsus.GetSubscription()

Comme vous le voyez une information n'est pas retournée, ce n'est rien d'autre que les paramètre du serveur auquel vous êtes connecté, afin de connaitre ces informations rien de plus simple:

($Wsus.GetSubscription()).UpdateServer

Ou

$Subscription = $Wsus.GetSubscription()
$Subscription.UpdateServer


 

Pour obtenir des informations relatives aux mises à jour (Updates) comme : le nombre de mise à jour, combien sont approuvées, combien sont déclinées, combien sont nécessaires... utilisez la commande:

  $Wsus.GetStatus()

Après ces quelques petits exemples, vous me direz c'est bien mais pour l'instant ce n'est que de la collecte d'informations, comment puis je exploiter le serveurs alors? 

Dans la prochaine partie nous verrons comment interagir avec le serveur WSUS et lancer le nettoyage (Cleanup) du serveur.