PowerShell Code Snippets

The solutions you find on the sysmanrec.com pages are provided “AS-IS” with no warranties. 

Run PowerShell scripts with sysnative in Intune

C:\Windows\Sysnative\WindowsPowerShell\v1.0\powershell.exe -noprofile -executionpolicy Bypass -file .\install.ps1

Change Password with remote connection

New-Object -COM Shell.Application).WindowsSecurity()

Start-Process with exit code
(Start-Process "msiexec.exe" -ArgumentList "/i FortiClientVPN.msi /passive /quiet INSTALLLEVEL=3 DESKTOPSHORTCUT=0 /NORESTART" -NoNewWindow -Wait -PassThru).ExitCode

Check if admin or system account

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
$isSystem = ($currentPrincipal.Identities.User.Value) -eq $SYSTEM_SID

Find Applications with a specific dependency (Java, .Net, c++ runtime, ….)

Let’s assume you want to update your Java Application and you have to find all the Applications in SCCM where Java is set as dependency. The script below will give you the list.

#Enter the Application that is set as dependency
$Dependency = '%Java%'

# Fill in your database name and database server below
$SCCMDBName = 'CM_P01'
$SCCMDBServerName = 'SVRCMP01'

$objConnection = New-Object -comobject ADODB.Connection
$objRecordset = New-Object -comobject ADODB.Recordset
$con = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Initial Catalog=$SCCMDBName;Data Source=$SCCMDBServerName"
$strSQL = @"
select rel.FromApplicationCIID from v_CIAppDependenceRelations as rel
inner join fn_ListLatestApplicationCIs(1033) AS app on app.CI_ID = rel.ToApplicationCIID
where app.DisplayName like '$Dependency'
"@

$objConnection.Open($con)
$objConnection.CommandTimeout = 0
# *********** Check If connection is open *******************
If($objConnection.state -eq 0)
{
Write-Host "Error: Connection to database failed. "
Exit 1
}
else
{
$CIID_Array = @()
$objRecordset.Open($strSQL,$objConnection)
$objRecordset.MoveFirst()
$rows=$objRecordset.RecordCount
do
{
$objRecordset.MoveNext()
$CIID = $objRecordset.Fields.Item(0).Value
$CIID_Array += $CIID
}
until ($objRecordset.EOF -eq $TRUE)
$objRecordset.Close()

foreach ($ID in $CIID_Array)
{
$strSQL2 = @"
select DisplayName from fn_ListLatestApplicationCIs(1033) where CI_ID like '$ID'
"@
$objRecordset.Open($strSQL2,$objConnection)
If($objConnection.state -eq 0)
{
Write-Host "Error: Connection to database failed. "
Exit 1
}
else
{
$value = $objRecordset.Fields.Item(0).Value
if($value)
{
Write-Host $value
}
$objRecordset.Close()
}
}
}

Copy an application 

The CM module doesn’t provide us a Copy-CMApplication function, therefore you have to use Get-CMApplication and New-CMApplication to build it. The code below will not copy the source files. You might think of Export-CMApplication and Emport-CMApplication as an alternative but if you just want to duplicate an application in the same environment you would have to rename the original application first since you can’t have two applications with the same name.

function Copy-CMApplication {
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Source,
[Parameter(Position=1)]
[System.String]
$Destination = "$Source - Copy"
)

New-CMApplication -Name $Destination

$smsAppNew = Get-CMApplication -Name $Source
$oldSDM = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($smsappNew.SDMPackageXML)

$smsAppDest = Get-CMApplication -Name $Destination
$newSDM = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($smsappDest.SDMPackageXML)

$newSDM.CopyFrom($oldSDM)
$newSDM.DeploymentTypes.ChangeId()
$newSDM.Title = $Destination
Set-SCCMApplication -name $Destination -app $newSDM
}

$AppName = '7-Zip 21.07 (MSI-x64)'
Copy-CMApplication -Source $AppName

Links:

The code below is based on Craig Shelton’s proposal: 

https://social.technet.microsoft.com/Forums/en-US/362ba113-105c-4bc4-b7a1-eaceee5253d1/copy-application-using-powershell?forum=configmanagersdk 

Create a scheduled task that cleans up IIS logs

By default, IIS creates a new log file every day and never deletes any old files. To limit the disk space of the files, it is therefore important to implement a cleanup task. The script below creates a scheduled task that will run once per week to execute a simple PowerShell command. There are lots of similar solutions available but the beauty of this one is that the scheduled task contains the code for the cleanup, so there’s no need to place a script on the disk. Mind that the code below will delete logs older than 30 days (therefore -30).

$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -command "& {Get-ChildItem -Path c:\inetpub\logs\logfiles\w3svc*\*.log | where {$_.LastWriteTime -lt (get-date).AddDays(-30)} | Remove-Item}"'
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Saturday -At 5am
$settingsSet = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
Register-ScheduledTask -User 'System' -Force -Action $action -Trigger $trigger -Settings $settingsSet -TaskName "CleanIISLogs" -Description "Weekly cleanup of IIS log"

Links:

Managing IIS Log File Storage: https://docs.microsoft.com/en-us/iis/manage/provisioning-and-managing-iis/managing-iis-log-file-storage

Use PowerShell to Create Scheduled Tasks: https://devblogs.microsoft.com/scripting/use-powershell-to-create-scheduled-tasks/

Manage IIS Log Files and purge those older than N days with a Powershell script or a batch file: https://www.ryadel.com/en/manage-iis-log-files-purge-older-n-days-bat-file/#google_vignette

Modify the maximum runtime of software updates in SCCM

A frequent complaint is that the maximum runtime of software updates is too short. Or too long in case that small maintenance windows are being used. In most cases, the maxruntime is 10, 60, or sometimes 120 min. The script below allows you to modify the value (the feature to adjust the value was added in 1706).

function LoadCMModule
{
Import-module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + '\ConfigurationManager.psd1')

$SiteCode = Get-PSDrive -PSProvider CMSITE
Set-location $SiteCode":"
}

$updates = Get-CMSoftwareUpdate -Name "*2020-04*" -Fast #Better set a filter, this takes quite long
$count = 0

foreach ($update in $updates )
{
Write-Host $update.LocalizedDisplayName $update.MaxExecutionTime

switch($update.MaxExecutionTime){
600 {Set-CMSoftwareUpdate -CI_ID $update.CI_ID -MaximumExecutionMins 15; $count++; Write-Host "Changing " $update.LocalizedDisplayName " Current MaximumExecutionMins: " $update.MaxExecutionTime -ForegroundColor Yellow; break;}
3600 {Set-CMSoftwareUpdate -CI_ID $update.CI_ID -MaximumExecutionMins 90; $count++; Write-Host "Changing " $update.LocalizedDisplayName " Current MaximumExecutionMins: " $update.MaxExecutionTime -ForegroundColor Yellow; break;}
7600 {Set-CMSoftwareUpdate -CI_ID $update.CI_ID -MaximumExecutionMins 180; $count++; Write-Host "Changing " $update.LocalizedDisplayName " Current MaximumExecutionMins: " $update.MaxExecutionTime -ForegroundColor Yellow; break;}
}
}
Write-Host $count " changes made" -ForegroundColor Green

Parsing Kerberos events

Parsing EventLog events is a bit tricky. The code snippets below shall help to parse events 4768 and 4769 with PowerShell on Domain Controllers (mind that only the first 100 events are requested)

Get 4768 events (authentication ticket (TGT) was requested):

Get-WinEvent -FilterHashtable @{LogName = 'Security';ID='4768'} | select -First 100 -Property * | ForEach-Object -Process {New-Object -TypeName PSObject -Property @{'TimeCreated'=$_.TimeCreated; 'Account Name'=$_.properties[0].Value; 
'Supplied Realm Name'=$_.properties[1].Value;

'User ID'=$_.properties[2].Value;
'Service Name'=$_.properties[3].Value;
'Service ID'=$_.properties[4].Value;
'Ticket Options'=$_.properties[5].Value;
'Result Code'='{0:x8}' -f $_.properties[5].Value;
'EncryptionType'='{0:x2}' -f $_.properties[7].Value}}

Link: 4768(S, F): A Kerberos authentication ticket (TGT) was requested
https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4768

Get 4769 events (A Kerberos service ticket was requested):

Get-WinEvent -FilterHashtable @{LogName = 'Security';ID='4769'} | select -First 100 -Property * | ForEach-Object -Process {New-Object -TypeName PSObject -Property @{'TimeCreated'=$_.TimeCreated;
'TargetUserName'=$_.properties[0].Value;
'TargetDomainName'=$_.properties[1].Value;
'ServiceName'=$_.properties[2].Value;
'ServiceSID'=$_.properties[3].Value;
'TicketOptions'='{0:x8}' -f $_.properties[4].Value;
'TicketEncryptionType'='{0:x2}' -f $_.properties[5].Value;
'IPAdress'=$_.properties[6].Value
'IPPort'=$_.properties[7].Value
'Status'=$_.properties[8].Value;
'Logon GUID'=$_.properties[9].Value;
'TransmittedServices'=$_.properties[10].Value}}


Link: 4769(S, F): A Kerberos service ticket was requested. https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4769

Little helpers:

Text Search

dir c:\windows\ccm\logs\*.log | Select-String 'MAX004'

Get .Net Framework 4 version

(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full').version
Get SID of an account:

$AdObj = New-Object System.Security.Principal.NTAccount('s-sql3@happyadmin.inc')
$SID = $AdObj.Translate([System.Security.Principal.SecurityIdentifier])
$SID.Value

Get account from SID:

$sid = 'S-1-5-21-291460342-2294237019-3453978477-502'
$objSID = New-Object System.Security.Principal.SecurityIdentifier($sid)
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
$objUser.Value

Last date an update was installed

((Get-HotFix |?{$_.InstalledOn -gt ((Get-Date).AddDays(-30))}).InstalledOn)[0].ToString("yyyyMMdd")

Monitor inboxes

$filewatcher = New-Object System.IO.FileSystemWatcher
#Mention the folder to monitor
$filewatcher.Path = "E:\Program Files\Microsoft Configuration Manager\inboxes" #Correct this!!
$filewatcher.Filter = "*.*"
#include subdirectories $true/$false
$filewatcher.IncludeSubdirectories = $true
$filewatcher.EnableRaisingEvents = $true

### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$writeaction = { $path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content "$PSScriptRoot\InboxWatch1.log" -value $logline
WriteLog -Path "$PSScriptRoot\InboxWatch.log" -Message ($logline | Out-String) -Component 'InboxWatch' -Type Info
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED

#The Register-ObjectEvent cmdlet subscribes to events that are generated by .NET objects on the local computer or on a remote computer.
#When the subscribed event is raised, it is added to the event queue in your session. To get events in the event queue, use the Get-Event cmdlet.
Register-ObjectEvent $filewatcher "Created" -Action $writeaction
Register-ObjectEvent $filewatcher "Changed" -Action $writeaction
Register-ObjectEvent $filewatcher "Deleted" -Action $writeaction
Register-ObjectEvent $filewatcher "Renamed" -Action $writeaction
while ($true) {sleep 5}
Script Template 
# Some Script
# ..., 25.10.2022

function Write-log {

[CmdletBinding()]
Param(
[parameter(Mandatory=$true)]
[String]$Path,

[parameter(Mandatory=$true)]
[String]$Message,

[parameter(Mandatory=$true)]
[String]$Component,

[Parameter(Mandatory=$true)]
[ValidateSet("Info", "Warning", "Error")]
[String]$Type
)

switch ($Type) {
"Info" { [int]$Type = 1 }
"Warning" { [int]$Type = 2 }
"Error" { [int]$Type = 3 }
}

# Create a log entry
$Content = "<![LOG[$Message]LOG]!>" +`
"<time=`"$(Get-Date -Format "HH:mm:ss.ffffff")`" " +`
"date=`"$(Get-Date -Format "M-d-yyyy")`" " +`
"component=`"$Component`" " +`
"context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +`
"type=`"$Type`" " +`
"thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +`
"file=`"`">"

# Write the line to the log file
Add-Content -Path $Path -Value $Content
}

<# Simple Log
function Log([string]$InhaltLog) {

Write-Host "$(Get-Date -Format "dd.MM.yyyy HH:mm:ss,ff") $($InhaltLog)"
Add-Content -Path $logFile -Value "$(Get-Date -Format "dd.MM.yyyy HH:mm:ss,ff") $($InhaltLog)"

}#>

$CompanyName = 'HappyAdmin'
$LogFilePath = 'c:\windows\logs\' + $CompanyName + '\LogName.log'

if((Test-Path('c:\windows\logs\' + $CompanyName)) -eq $false){New-Item -ItemType Directory -Path "c:\windows\logs\$CompanyName"}

Write-Log -Path $LogFilePath -Message 'Starting Blabla, script version 1.0' -Component $MyInvocation.MyCommand.Name -Type Info
Write-Log -Path $LogFilePath -Message "Ending Script" -Component $MyInvocation.MyCommand.Name -Type Info