Search This Blog

Powershell - Logon- and Startup Script Manager (LSSM)

HOW IT WORKS
LSSM is a PowerShell script that can control other startup and logon scripts, using a Central GPO deployed to computers, with Loopback processing enabled (this policy enables the execution of logon scripts while the GPO is linked only to OUs containing Computer objects).

Its task is the running of global- or member-based scripts with logs created on the local computer, while also allowing exceptions from global scripts. The way this has been achieved is by using AD Security groups with special naming convention. A User- or Computer object, when starting up or logging in, will have their “member of” list checked in AD (LDAP connection) then evaluated by the LSSM. If the name of the script is the same as with the LSSM AD Groups name ends with, then it will be executed, or in case of global script, if the same happens, instead of running the script, it will be blocked, thereby allowing exclusion from global scripts.

HOW TO SETUP
  • Create a globally available share for the scripts, with following folder structure:

    • COMPUTER accounts and USER accounts must have read access to this share.
    • My suggestion is to use either the NETLOGON share or the folder of the GPO that will hold the LSSM scripts. Just create an LSSM folder next to the "User" and "Machine" folder.
  • Create a GPO and enable "Loopback processing" with "Merge" option, then add the ps1 files to it. LSSM_Startup.ps1 under computer configuration as "startup script" and LSSM_Logon.ps1 under User Configuration as a "logon script".
  • Link the GPO to all OUs containing computers
  • Create the OU strukture for the LSSM Security groups. My suggestion:

  • Create a script (try using a 4-digit prefix for the name as this can be used as a priority setting, determining the execution order), place it in the appropriate LSSM share folder and create a security group for it, using the below described naming convention.


SUPPORTED FILES
The LSSM currently supports the following file extensions:
  • *.ps1
  • *.cmd
  • *.bat
  • *.vbs

IMPORTANT: Powershell files, while easy to write and very useful, because it is so intelligent, the run-time of them is enormous, which is why it is not recommended using them as logon- or startup scripts.

LOGGING
LSSM creates a log file under C:\Windows\Temp\_LSSM\ and logs various details from the machine, user and the scripts run. Logfiles older than 7 days will be deleted. The content of the logfile is the following:

  • Script version
  • Script type
  • Hostname
  • Username
  • Exact date and time the script started
  • Script directory
  • Time each script required from start to finish
  • Overall run time
  • List of scripts run
  • List of scripts blocked/excluded

AD SECURITY GROUP NAMING



  • (1.) First part: Not used in the script, so it can be used as a identification prefix. Example Organization_LSSM
  • (2.) Second part: shows that this group is used by LSSM. DO NOT CHANGE THIS AS THIS IS HOW THE SCRIPT FINDS GROUPS RELATED TO LSSM!!
  • (3.) Third part: Shows the targeted object
    • U: User script
    • C: Computer script
  • (4.) Forth part: Type of the script
    • M: Member-based script
    • E: Exclusion from global script
  • (5.) Fifth part: The name of the script without the file extensions (you can use any character here, even "_" symbol)
Here are the naming possibilities:
  • Organization_LSSM_U_M_0010_TestScript: Member-based User logon script. Members of this group will run the logon script named 0010_TestScript
  • Organization_LSSM_C_M_0010_TestScript: Member-based Computer startup script. Members of this group will run the startup script named 0010_TestScript
  • Organization_LSSM_U_E_0010_TestScript: Global User Exclusion group. For members of this AD Group, the logon script 0010_TestScript will be skipped.
  • Organization_LSSM_C_E_0010_TestScript: Global Computer exclusion group. For members of this AD Group, the startup script 0010_TestScript will be skipped.

LSSM-Logon.ps1:
##########################################################################
# LOGON SCRIPT MANAGER
##########################################################################
# Created by: Janos Friedrich
# Created on: 2018-06-29
# Revision: 1.0.0
# Description: Script is responsible to control and run multiple scripts
# at logon or startup, with the ability to taget only specific members of specific groups
##########################################################################
# VERSION CONTROL
##########################################################################
# 2020.01.06 - v1.0 - Script created by Janos Friedrich
##########################################################################

$ErrorActionPreference = 'silentlycontinue'

##########################################################################
# GLOBAL VARIABLES
##########################################################################

$ScriptName = "Logon Script Manager"
$ScriptVersion = "1.0.0"
$WorkDir = "C:\Windows\Temp\_LSSM"
$LogDate = Get-Date -Format yyyyMMdd
$LogDateBackup = Get-Date -Format yyyyMMddHHmmss
$Log = "GLSS_$LogDate.log"
$LogDir = $WorkDir + $Log
$GLStartDate = Get-Date
$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition
$i = 0
$ErrorOccured = $false

##########################################################################
# FUNCTIONS
##########################################################################

function Output-Log
{
    param( [string]$LogData  )
    $LogDateFormat = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
    $LogDateFormat + " - " + $LogData >> $LogDir
}

##########################################################################
# SCRIPT BODY
##########################################################################

# Create log folder if it does not exists
If(!(test-path $WorkDir )) { New-Item -ItemType Directory -Force -Path $WorkDir }

# Remove old logs
$limit = (Get-Date).AddDays(-7)
Get-ChildItem -Path $WorkDir -Force -Filter GLSS* | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force

##########################################################################
# RUNNING GLOBAL LOGON SCRIPTS
##########################################################################
$Log = "GLSS_LOGON_$LogDate.log"
$LogDir = $WorkDir + $Log

Output-Log -LogData ("------------------------------------------------")
Output-Log -LogData ("Script name: " + $ScriptName)
Output-Log -LogData ("Script version: " + $ScriptVersion)
Output-Log -LogData ("Script Type: LOGON")
Output-Log -LogData ("Script running on " + (hostname))
Output-Log -LogData ("Script running at " + (get-date))
Output-Log -LogData ("Script running by " + (whoami))
Output-Log -LogData ("Script path " + ($ScriptPath))
Output-Log -LogData ("------------------------------------------------")

$Include=@("*.ps1","*.cmd","*.bat","*.vbs")
$GlobalLogonScripts = Get-ChildItem "$ScriptPath\Global_Logon\*.*" -Include $Include
$MemberbasedLogonScripts = Get-ChildItem "$ScriptPath\Memberbased_Logon\*.*" -Include $Include

Try
{
    Output-Log -LogData ( "Retrieving MemberOf List using .NET..." )
    $UserGroupsNET = ([System.DirectoryServices.DirectorySearcher]"(&(objectCategory=User)(sAMAccountName=$env:UserName))").FindAll().Properties["MemberOf"]
    $UserGroups = @()
    foreach ($UserGroupNET in $UserGroupsNET) {
        $Int = $UserGroupNET.IndexOf(",")
        $UserGroups += New-Object psobject -Property @{ Name = $UserGroupNET.Substring(3,$Int - 3) }
    }
    $UserGroups = $UserGroups | where {$_.Name -like "*LSSM_*"}
}
Catch
{
    $ErrorMessage = $_.Exception.Message
    Output-Log -LogData ( "There was an error retrieving the Membership list from AD. Error Message: $ErrorMessage" )
    $ErrorOccured=$true
}

if($ErrorOccured) {
    Output-Log -LogData ( "Retrieving MemberOf List using PowerShell..." )
    $UserGroups = GET-ADUser -Identity $env:UserName –Properties MemberOf | Select-Object -ExpandProperty MemberOf | Get-ADGroup -Properties name | Select-Object name | where {$_.Name -like "RA046_GLSS_*"}
}

$Exclusions = @()
foreach ( $UserGroup in $UserGroups )
{
 
    If ($UserGroup.Name.Split("_")[3] -eq "E")
    {
        $Exclusions += New-Object psobject -Property @{ FileName = $UserGroup.Name.Substring(15,$UserGroup.Length - 15) }
    }
}

Output-Log -LogData ("/////GLOBAL LOGON SCRIPTS/////")
$x = 0

Output-Log -LogData ("LSSM Groups: " + ($UserGroups.Name -join ";"))
Output-Log -LogData ("Scripts excluded from: " + ($Exclusions.FileName -join ";"))

foreach ( $GlobalLogonScript in $GlobalLogonScripts ) {

    If ( $Exclusions.FileName -notcontains $GlobalLogonScript.BaseName )
    {
        Output-Log -LogData ("Starting script '" + $GlobalLogonScript.Name + "':")
        $ScriptStartDate = Get-Date
     
        "------------------------------------------------" >> $LogDir

        Switch ($GlobalLogonScript.Extension) {
        ".ps1" { powershell.exe -File $GlobalLogonScript.FullName >> $LogDir }
        ".cmd" { cmd /c $GlobalLogonScript.FullName >> $LogDir }
        ".bat" { cmd /c $GlobalLogonScript.FullName >> $LogDir }
        ".vbs" { & cscript.exe /nologo $GlobalLogonScript.FullName >> $LogDir }
        }
     
        "------------------------------------------------" >> $LogDir
 
        $ScriptEndDate = Get-Date
        $ScriptRunTime = New-TimeSpan -Start $ScriptStartDate -End $ScriptEndDate
        Output-Log -LogData ("Stopping script. '" + $GlobalLogonScript.Name + "'. Total runtime: $ScriptRunTime")
        $x = $x + 1
        $i = $i + 1
    } else {
        Output-Log -LogData ("Script '" + $GlobalLogonScript.Name + "' blocked from running.")
    }

}
Output-Log -LogData ("Global Logon Scripts run: " + $x)

##########################################################################
# RUNNING MEMBERBASED LOGON SCRIPTS
##########################################################################

Output-Log -LogData ("/////MEMBERBASED LOGON SCRIPTS/////")
$x = 0

foreach ( $MemberbasedLogonScript in $MemberbasedLogonScripts ) {

    $Found = $false
 
    foreach ( $UserGroup in $UserGroups ) {

        [string]$Strint1 = $UserGroup.Name.Substring(15, $UserGroup.Name.Length - 15)
        [string]$String2 = $MemberbasedLogonScript.BaseName

        IF ( $Strint1.ToLower().Equals($String2.ToLower()) )
        {
            $Found = $true
        }
    }

    if ($Found) {
        Output-Log -LogData ("Starting script '" + $MemberbasedLogonScript.Name + "':")
        $ScriptStartDate = Get-Date

        "------------------------------------------------" >> $LogDir

        Switch ($MemberbasedLogonScript.Extension) {
        ".ps1" { powershell.exe -File $MemberbasedLogonScript.FullName >> $LogDir }
        ".cmd" { cmd /c $MemberbasedLogonScript.FullName >> $LogDir }
        ".bat" { cmd /c $MemberbasedLogonScript.FullName >> $LogDir }
        ".vbs" { & cscript.exe /nologo $MemberbasedLogonScript.FullName >> $LogDir }
        }
     
        "------------------------------------------------" >> $LogDir

        $ScriptEndDate = Get-Date
        $ScriptRunTime = New-TimeSpan -Start $ScriptStartDate -End $ScriptEndDate
        Output-Log -LogData ("Stoping script '" + $MemberbasedLogonScript.Name + "'. Total runtime: $ScriptRunTime")
        $x = $x + 1
        $i = $i + 1
    }

}
Output-Log -LogData ("Memberbased logon Scripts run: " + $x)

##########################################################################
# POST SCRIPTS
##########################################################################

$GLEndDate = Get-Date
$GLRunTime = New-TimeSpan -Start $GLStartDate -End $GLEndDate
Output-Log -LogData ("------------------------------------------------")
Output-Log -LogData ("Total number of scripts run: $i")
Output-Log -LogData ("Stopping Logon Scripts. Total runtime: $GLRunTime")
Output-Log -LogData ("------------------------------------------------")


*************************************************************************


LSSM-Startup.ps1:
##########################################################################
# STARTUP SCRIPT MANAGER
##########################################################################
# Created by: Janos Friedrich
# Created on: 2018-06-29
# Revision: 1.0.0
# Description: Script is responsible to control and run multiple scripts
# at logon or startup, with the ability to taget only specific members of specific groups
##########################################################################
# VERSION CONTROL
##########################################################################
# 2018.06.29 - v1.0 - Script created by Janos Friedrich
##########################################################################

$ErrorActionPreference = 'silentlycontinue'

##########################################################################
# GLOBAL VARIABLES
##########################################################################

$ScriptName = "Startup Script Manager"
$ScriptVersion = "1.0.0"
$WorkDir = "C:\Windows\Temp\_LSSM\"
$LogDate = Get-Date -Format yyyyMMdd
$LogDateBackup = Get-Date -Format yyyyMMddHHmmss
$Log = "GLSS_$LogDate.log"
$LogDir = $WorkDir + $Log
$GLStartDate = Get-Date
$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition
$i = 0
$ErrorOccured = $false

##########################################################################
# FUNCTIONS
##########################################################################

function Output-Log
{
    param( [string]$LogData  )
    $LogDateFormat = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
    $LogDateFormat + " - " + $LogData >> $LogDir
}

##########################################################################
# SCRIPT BODY
##########################################################################

# Create log folder if it does not exists
If(!(test-path $WorkDir )) { New-Item -ItemType Directory -Force -Path $WorkDir }

# Remove old logs
$limit = (Get-Date).AddDays(-7)
Get-ChildItem -Path $WorkDir -Force -Filter LSSM* | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force

##########################################################################
# RUNNING GLOBAL STARTUP SCRIPTS
##########################################################################
     
$Log = "LSSM_STARTUP_$LogDate.log"
$LogDir = $WorkDir + $Log

Output-Log -LogData ("------------------------------------------------")
Output-Log -LogData ("Script name: " + $ScriptName)
Output-Log -LogData ("Script version: " + $ScriptVersion)
Output-Log -LogData ("Script Type: STARTUP")
Output-Log -LogData ("Script running on " + (hostname))
Output-Log -LogData ("Script running at " + (get-date))
Output-Log -LogData ("Script running by " + (whoami))
Output-Log -LogData ("Script path " + ($ScriptPath))
Output-Log -LogData ("------------------------------------------------")

$Include=@("*.ps1","*.cmd","*.bat","*.vbs")
$GlobalStartupScripts = Get-ChildItem "$ScriptPath\Global_Startup\*.*" -Include $Include
$MemberbasedStartupScripts = Get-ChildItem "$ScriptPath\Memberbased_Startup\*.*" -Include $Include

Try
{
    Output-Log -LogData ( "Retrieving MemberOf List using .NET..." )
    $ComputerGroupsNET = ([System.DirectoryServices.DirectorySearcher]"(&(objectCategory=Computer)(name=$env:COMPUTERNAME))").FindAll().Properties["MemberOf"]
    $ComputerGroups = @()
    foreach ($ComputerGroupNET in $ComputerGroupsNET) {
        $Int = $ComputerGroupNET.IndexOf(",")
        $ComputerGroups += New-Object psobject -Property @{ Name = $ComputerGroupNET.Substring(3,$Int - 3) }
    }
    $ComputerGroups = $ComputerGroups | where {$_.Name -like "*LSSM_*"}
}
Catch
{
    $ErrorMessage = $_.Exception.Message
    Output-Log -LogData ( "There was an error retrieving the Membership list from AD. Error Message: $ErrorMessage" )
    $ErrorOccured = $true
}

if($ErrorOccured) {
    Output-Log -LogData ( "Retrieving MemberOf List using PowerShell..." )
    $ComputerGroups = GET-ADComputer -Identity $env:COMPUTERNAME –Properties MemberOf | Select-Object -ExpandProperty MemberOf | Get-ADGroup -Properties name | Select-Object name | where {$_.Name -like "RA046_GLSS_*"} -ErrorAction Stop
}
     
$Exclusions = @()
foreach ( $ComputerGroup in $ComputerGroups )
{
    If ($ComputerGroup.Name.Split("_")[3] -eq "E")
    {
        $Exclusions += New-Object psobject -Property @{ FileName = $ComputerGroup.Name.Substring(15, $ComputerGroup.Length - 15) }
    }
}

Output-Log -LogData ("/////GLOBAL STARTUP SCRIPTS/////")
$x = 0

Output-Log -LogData ("GLSS Groups: " + ($ComputerGroups.Name -join ";"))
Output-Log -LogData ("Scripts excluded from: " + ($Exclusions.FileName -join ";"))

foreach ( $GlobalStartupScript in $GlobalStartupScripts ) {
         
    If ( $Exclusions.FileName -notcontains $GlobalStartupScript.BaseName )
    {
        Output-Log -LogData ("Starting script '" + $GlobalStartupScript.Name + "':")
        $ScriptStartDate = Get-Date

        "------------------------------------------------" >> $LogDir

        Switch ($GlobalStartupScript.Extension) {
        ".ps1" { powershell.exe -File $GlobalStartupScript.FullName >> $LogDir }
        ".cmd" { cmd /c $GlobalStartupScript.FullName >> $LogDir }
        ".bat" { cmd /c $GlobalStartupScript.FullName >> $LogDir }
        ".vbs" { & cscript.exe /nologo $GlobalStartupScript.FullName >> $LogDir }
        }
     
        "------------------------------------------------" >> $LogDir

        $ScriptEndDate = Get-Date
        $ScriptRunTime = New-TimeSpan -Start $ScriptStartDate -End $ScriptEndDate
        Output-Log -LogData ("Stopping script '" + $GlobalStartupScript.Name + "'. Total runtime: $ScriptRunTime")
        $x = $x + 1
        $i = $i + 1
    } else {
        Output-Log -LogData ("Script '" + $GlobalStartupScript.Name + "' blocked from running.")
    }

}
Output-Log -LogData ("Global Startup Scripts run: " + $x)

##########################################################################
# RUNNING MEMBERBASED STARTUP SCRIPTS
##########################################################################

Output-Log -LogData ("/////MEMBERBASED STARTUP SCRIPTS/////")
$x = 0

foreach ( $MemberbasedStartupScript in $MemberbasedStartupScripts ) {
 
    $Found = $false

    foreach ( $ComputerGroup in $ComputerGroups ) {

        [string]$String1 = $ComputerGroup.Name.Substring(15, $ComputerGroup.Name.Length - 15)
        [string]$String2 = $MemberbasedStartupScript.BaseName

        IF ( $String1.ToLower().Equals($String2.ToLower()) )
        {
            $Found = $true
        }
    }

    if ($Found) {
        Output-Log -LogData ("Starting script '" + $MemberbasedStartupScript.Name + "':")
        $ScriptStartDate = Get-Date

        "------------------------------------------------" >> $LogDir

        Switch ($MemberbasedStartupScript.Extension) {
        ".ps1" { powershell.exe -File $MemberbasedStartupScript.FullName >> $LogDir }
        ".cmd" { cmd /c $MemberbasedStartupScript.FullName >> $LogDir }
        ".bat" { cmd /c $MemberbasedStartupScript.FullName >> $LogDir }
        ".vbs" { & cscript.exe /nologo $MemberbasedStartupScript.FullName >> $LogDir }
        }
     
        "------------------------------------------------" >> $LogDir

        $ScriptEndDate = Get-Date
        $ScriptRunTime = New-TimeSpan -Start $ScriptStartDate -End $ScriptEndDate
        Output-Log -LogData ("Stopping script '" + $MemberbasedStartupScript.Name + "'. Total runtime: $ScriptRunTime")
        $x = $x + 1
        $i = $i + 1
    }
}
Output-Log -LogData ("Memberbased Startup Scripts run: " + $x)

##########################################################################
# POST SCRIPTS
##########################################################################

$GLEndDate = Get-Date
$GLRunTime = New-TimeSpan -Start $GLStartDate -End $GLEndDate
Output-Log -LogData ("------------------------------------------------")
Output-Log -LogData ("Total number of scripts run: $i")
Output-Log -LogData ("Stopping Startup Scripts. Total runtime: $GLRunTime")
Output-Log -LogData ("------------------------------------------------")

No comments:

Post a Comment