Last active
January 3, 2019 15:54
-
-
Save Ioan-Popovici/e6c873bb568036eff22f6d1ac9be66c5 to your computer and use it in GitHub Desktop.
Adds devices from a txt file to a SCCM device collection, triggered when the txt file is saved.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Adds devices from a txt file to a SCCM device collection. | |
.DESCRIPTION | |
Adds devices from a txt file to a SCCM device collection, triggered when the txt file is saved. | |
.EXAMPLE | |
Add-CMDeviceDirectMemebershipRules.ps1 | |
.INPUTS | |
System.String | |
.OUTPUTS | |
System.String. | |
.NOTES | |
Created by Ioan Popovici | |
Requirements | |
* SCCM client SDK | |
* Local file system only | |
To do | |
* Remove direct membership rules using CIM. | |
* Add option to add new devices without removing all direct membership rules first. | |
.LINK | |
https://SCCM.Zone/Add-CMDeviceDirectMemebershipRules | |
.LINK | |
https://SCCM.Zone/Add-CMDeviceDirectMemebershipRules-CHANGELOG | |
.LINK | |
https://SCCM.Zone/Add-CMDeviceDirectMemebershipRules-GIT | |
.LINK | |
https://SCCM.Zone/Issues | |
.COMPONENT | |
CM | |
.FUNCTIONALITY | |
Add devices to a collection | |
#> | |
## Set script requirements | |
#Requires -Version 3.0 | |
##*============================================= | |
##* VARIABLE DECLARATION | |
##*============================================= | |
#region VariableDeclaration | |
## Get script path and name | |
[String]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) | |
[String]$ScriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition) | |
## CSV and log files initialization | |
# Set the CSV Settings and Data file name | |
[String]$csvDataFileName = $ScriptName | |
[String]$csvSettingsFileName = $ScriptName+'Settings' | |
# Get CSV Settings and Data file name with extension | |
[String]$csvDataFileNameWithExtension = $csvDataFileName+'.csv' | |
[String]$csvSettingsFileNameWithExtension = $csvSettingsFileName+'.csv' | |
# Assemble CSV Settings and Data file path | |
[String]$csvDataFilePath = (Join-Path -Path $ScriptPath -ChildPath $csvDataFileName)+'.csv' | |
[String]$csvSettingsFilePath = (Join-Path -Path $ScriptPath -ChildPath $csvSettingsFileName)+'.csv' | |
# Assemble log file Path | |
[String]$LogFilePath = (Join-Path -Path $ScriptPath -ChildPath $ScriptName)+'.log' | |
## Initialize last write reference time with current time | |
[DateTime]$LastWriteTimeReference = (Get-Date) | |
## Global error result array list | |
[System.Collections.ArrayList]$Global:ErrorResult = @() | |
#endregion | |
##*============================================= | |
##* END VARIABLE DECLARATION | |
##*============================================= | |
##*============================================= | |
##* FUNCTION LISTINGS | |
##*============================================= | |
#region FunctionListings | |
#region Function Write-Log | |
Function Write-Log { | |
<# | |
.SYNOPSIS | |
Writes data to file log, event log and console. | |
.DESCRIPTION | |
Writes data to file log, event log and console. | |
.PARAMETER EventLogEntryMessage | |
The event log entry message. | |
.PARAMETER EventLogName | |
The event log to write to. | |
.PARAMETER FileLogName | |
The file log name to write to. | |
.PARAMETER EventLogEntrySource | |
The event log entry source. | |
.PARAMETER EventLogEntryID | |
The event log entry ID. | |
.PARAMETER EventLogEntryType | |
The event log entry type (Error | Warning | Information | SuccessAudit | FailureAudit). | |
.PARAMETER SkipEventLog | |
Skip writing to event log. | |
.EXAMPLE | |
Write-Log -EventLogEntryMessage 'Set-ClientMW was successful' -EventLogName 'Configuration Manager' -EventLogEntrySource 'Script' -EventLogEntryID '1' -EventLogEntryType 'Information' | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$false,Position=0)] | |
[Alias('Message')] | |
[String]$EventLogEntryMessage, | |
[Parameter(Mandatory=$false,Position=1)] | |
[Alias('EName')] | |
[String]$EventLogName = 'Configuration Manager', | |
[Parameter(Mandatory=$false,Position=2)] | |
[Alias('Source')] | |
[String]$EventLogEntrySource = $ScriptName, | |
[Parameter(Mandatory=$false,Position=3)] | |
[Alias('ID')] | |
[int32]$EventLogEntryID = 1, | |
[Parameter(Mandatory=$false,Position=4)] | |
[Alias('Type')] | |
[String]$EventLogEntryType = 'Information', | |
[Parameter(Mandatory=$false,Position=5)] | |
[Alias('SkipEL')] | |
[switch]$SkipEventLog | |
) | |
## Initialization | |
# Getting the date and time | |
[String]$LogTime = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss').ToString() | |
# Archive log file if it exists and it's larger than 50 KB | |
If ((Test-Path $LogFilePath) -and (Get-Item $LogFilePath).Length -gt 50KB) { | |
Get-ChildItem -Path $LogFilePath | Rename-Item -NewName { $_.Name -Replace '.log','.lo_' } -Force | |
} | |
# Create event log and event source if they do not exist | |
If (-not ([System.Diagnostics.EventLog]::Exists($EventLogName)) -or (-not ([System.Diagnostics.EventLog]::SourceExists($EventLogEntrySource)))) { | |
# Create new event log and/or source | |
New-EventLog -LogName $EventLogName -Source $EventLogEntrySource | |
} | |
## Error logging | |
If ($_.Exception) { | |
# Write to log | |
Write-EventLog -LogName $EventLogName -Source $EventLogEntrySource -EventId $EventLogEntryID -EntryType 'Error' -Message "$EventLogEntryMessage `n$_" | |
# Write to console | |
Write-Host `n$EventLogEntryMessage -BackgroundColor Red -ForegroundColor White | |
Write-Host $_.Exception -BackgroundColor Red -ForegroundColor White | |
# Assemble log file line | |
[String]$LogLine = "$LogTime : $_.Exception" | |
# Write to log file | |
$LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Continue' | |
# Add to error result array | |
$Global:ErrorResult.Add($LogLine) | |
# Breaking Cycle so we don't get stuck in a loop :) | |
Break | |
} | |
Else { | |
# Skip event log if requested | |
If ($SkipEventLog) { | |
# Write to console | |
Write-Host $EventLogEntryMessage -BackgroundColor White -ForegroundColor Blue | |
} | |
Else { | |
# Write to event log | |
Write-EventLog -LogName $EventLogName -Source $EventLogEntrySource -EventId $EventLogEntryID -EntryType $EventLogEntryType -Message $EventLogEntryMessage | |
# Write to console | |
Write-Host $EventLogEntryMessage -BackgroundColor White -ForegroundColor Blue | |
} | |
} | |
## Assemble log file line | |
[String]$LogLine = "$LogTime : $EventLogEntryMessage" | |
## Write to log file | |
$LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Continue' | |
} | |
#endregion | |
#region Function Send-Mail | |
Function Send-Mail { | |
<# | |
.SYNOPSIS | |
Send E-Mail to specified address. | |
.DESCRIPTION | |
Send E-Mail body to specified address. | |
.PARAMETER From | |
Source. | |
.PARAMETER To | |
Destination. | |
.PARAMETER CC | |
Carbon copy. | |
.PARAMETER Body | |
E-Mail body. | |
.PARAMETER Attachments | |
E-Mail Attachments. | |
.PARAMETER SMTPServer | |
E-Mail SMTPServer. | |
.PARAMETER SMTPPort | |
E-Mail SMTPPort. | |
.EXAMPLE | |
Send-Mail -From '[email protected]' -To "[email protected]" -Subject "test" -Body 'Test' -CC '[email protected]' -Attachments 'C:\Temp\test.log' | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true,Position=0)] | |
[String]$From, | |
[Parameter(Mandatory=$true,Position=1)] | |
[String]$To, | |
[Parameter(Mandatory=$false,Position=2)] | |
[String]$CC, | |
[Parameter(Mandatory=$true,Position=3)] | |
[String]$Subject, | |
[Parameter(Mandatory=$true,Position=4)] | |
[String]$Body, | |
[Parameter(Mandatory=$false,Position=5)] | |
[String]$Attachments = $LogFilePath, | |
[Parameter(Mandatory=$false,Position=6)] | |
[String]$SMTPServer = 'mail.datakraftverk.no', | |
[Parameter(Mandatory=$false,Position=7)] | |
[String]$SMTPPort = "25" | |
) | |
## Send mail with error handling | |
Try { | |
# With CC | |
If ($CC -ne [String]::Empty -and $CC -ne 'NO') { | |
Send-MailMessage -From $From -To $To -Subject $Subject -CC $CC -Body $Body -Attachments $Attachments -SmtpServer $SMTPServer -Port $SMTPPort -ErrorAction 'Stop' | |
} | |
# Without CC | |
Elseif ($CC -eq [String]::Empty -or $CC -eq 'NO') { | |
Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -Attachments $Attachments -SmtpServer $SMTPServer -Port $SMTPPort -ErrorAction 'Stop' | |
} | |
} | |
Catch { | |
Write-Log -Message 'Send Mail - Failed!' | |
} | |
} | |
#endregion | |
#region Function Get-DeviceDirectMembershipRules | |
Function Get-DeviceDirectMembershipRules { | |
<# | |
.SYNOPSIS | |
Get ALL existing device direct membership rules. | |
.DESCRIPTION | |
Get ALL existing device direct membership rules from a collection. | |
.PARAMETER CollectionName | |
The collection name for which to get device direct membership rules. | |
.EXAMPLE | |
Get-DeviceDirectMembershipRules -CollectionName 'Computer Collection' | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true,Position=0)] | |
[Alias('Collection')] | |
[String]$CollectionName | |
) | |
## Get collection ID | |
Try { | |
$CollectionID = (Get-CMDeviceCollection -Name $CollectionName -ErrorAction 'Stop').CollectionID | |
} | |
Catch { | |
Write-Log -Message "Getting $CollectionName ID - Failed!" | |
} | |
## Get collection direct membership | |
Try { | |
Get-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ErrorAction 'Stop' | |
} | |
Catch { | |
# write to log | |
Write-Log -Message "Get Device Direct Membership Rules for $CollectionName - Failed!" | |
} | |
} | |
#endregion | |
#region Function Remove-DeviceDirectMembershipRule | |
Function Remove-DeviceDirectMembershipRule { | |
<# | |
.SYNOPSIS | |
Remove ALL existing device direct membership rules. | |
.DESCRIPTION | |
Remove ALL existing device direct membership rules from a collection. | |
.PARAMETER CollectionName | |
The collection name for which to remove the device direct membership rules. | |
.EXAMPLE | |
Remove-DeviceDirectMembershipRule -CollectionName 'Computer Collection' | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
Blog : https://SCCM.Zone | |
.LINK | |
Github : https://SCCM.Zone/Git | |
.LINK | |
Issues : https://SCCM.Zone/Issues | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true,Position=0)] | |
[Alias('Collection')] | |
[String]$CollectionName | |
) | |
## Get collection ID | |
Try { | |
$CollectionID = (Get-CMDeviceCollection -Name $CollectionName -ErrorAction 'Stop').CollectionID | |
} | |
Catch { | |
Write-Log -Message "Getting $CollectionName ID - Failed!" | |
} | |
## Get collection direct membership rules and delete them | |
Try { | |
Get-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ErrorAction 'Stop' | ForEach-Object { | |
Remove-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ResourceName $_.RuleName -ErrorAction 'Stop' -Force | |
# Write to log | |
Write-Log -Message ($_.RuleName+' - Removed!') -SkipEventLog | |
} | |
} | |
Catch { | |
# write to log | |
Write-Log -Message "$_.RuleName - Removal Failed!" | |
} | |
} | |
#endregion | |
Function Add-DeviceDirectMembershipRule { | |
<# | |
.SYNOPSIS | |
Add device direct membership rules. | |
.DESCRIPTION | |
Add device direct membership rules to a collection. | |
.PARAMETER CollectionName | |
The collection name for which add device direct membership rules. | |
.PARAMETER DeviceName | |
The device name for which add the device direct membership rule. | |
.EXAMPLE | |
Add-DeviceDirectMembershipRule -CollectionName 'Computer Collection' -DeviceName 'SomeComputer' | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true,Position=0)] | |
[Alias('Collection')] | |
[String]$CollectionName, | |
[Alias('Device')] | |
[String]$DeviceName | |
) | |
## Get collection ID | |
Try { | |
$CollectionID = (Get-CMDeviceCollection -Name $CollectionName -ErrorAction 'Stop').CollectionID | |
} | |
Catch { | |
Write-Log -Message "Getting $CollectionName ID - Failed!" | |
} | |
## Add device direct membership rules to the specified collection | |
Try { | |
Add-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ResourceID (Get-CMDevice -Name $DeviceName).ResourceID -ErrorAction 'Stop' | |
# Write to log | |
Write-Log -Message ($DeviceName+' - Added!') -SkipEventLog | |
} | |
Catch { | |
# write to log | |
Write-Log -Message "Adding $DeviceName to $CollectionName - Failed!" | |
} | |
} | |
#endregion | |
#region Function Start-DataProcessing | |
Function Start-DataProcessing { | |
<# | |
.SYNOPSIS | |
Used for main data processing. | |
.DESCRIPTION | |
Used for main data processing, for this script only. | |
.EXAMPLE | |
Start-DataProcessing | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
## Import SCCM PSH module and changing context | |
Try { | |
Import-Module $env:SMS_ADMIN_UI_PATH.Replace('\bin\i386','\bin\configurationmanager.psd1') -ErrorAction 'Stop' | |
} | |
Catch { | |
Write-Log -Message 'Importing SCCM PSH module - Failed!' | |
} | |
# Get the CMSITE SiteCode and change connection context | |
$SiteCode = Get-PSDrive -PSProvider CMSITE | |
# Change the connection context | |
Set-Location "$($SiteCode.Name):\" | |
## Import the Settings csv file | |
Try { | |
$csvSettingsData = Import-Csv -Path $csvSettingsFilePath -Encoding 'UTF8' -ErrorAction 'Stop' | |
} | |
Catch { | |
# write to log | |
Write-Log -Message 'Importing Settings CSV Data - Failed!' | |
} | |
## Import the Device csv file | |
Try { | |
$csvDeviceData = Import-Csv -Path $csvDataFilePath -Encoding 'UTF8' -ErrorAction 'Stop' | |
} | |
Catch { | |
# write to log | |
Write-Log -Message 'Importing Device CSV Data - Failed!' | |
} | |
## Process imported csv data | |
Try { | |
# Remove ALL device direct membership rules from specified unique collections | |
$csvDeviceData.CollectionName | Select-Object -Unique | ForEach-Object { | |
Remove-DeviceDirectMembershipRule -CollectionName $_ | |
} | |
# Add device direct membership rules to specified collections | |
$csvDeviceData | ForEach-Object { | |
Add-DeviceDirectMembershipRule -CollectionName $_.CollectionName -DeviceName $_.DeviceName | |
} | |
# Initialize result array | |
[array]$Result =@() | |
# Parsing CSV unique collection names | |
$csvDeviceData.CollectionName | Select-Object -Unique | ForEach-Object { | |
# Getting maintenance windows for collection (split to new line) | |
$DeviceDirectMemebershipRules = Get-DeviceDirectMembershipRules -CollectionName $_ | ForEach-Object { $_.RuleName+"`n" } | |
# Assemble result with descriptors | |
$Result+= "`nListing all Device Direct Memebership Rules for: "+$_+" "+"`n`n "+$DeviceDirectMemebershipRules | |
} | |
# Convert the result to String and write it to log | |
[String]$ResultString = Out-String -InputObject $Result | |
Write-Log -Message $ResultString | |
# Return to Script Path | |
Set-Location $ScriptPath | |
# Remove SCCM PSH Module | |
Remove-Module 'ConfigurationManager' -Force -ErrorAction 'Continue' | |
} | |
Catch { | |
# Not needed, empty | |
} | |
# Block will always be processed, used for sending e-mail with failure or success reports | |
Finally { | |
# Send Mail Report if needed | |
If ($csvSettingsData.SendMail -eq 'YES' -and -not $Global:ErrorResult) { | |
# Write to log | |
Write-Log -Message "Sending Mail Report..." | |
# Sending mail | |
Send-Mail -Subject 'Info: Adding Device Direct Membership Rules to Collection - Success!' -Body $ResultString -From $csvSettingsData.From -To $csvSettingsData.To -CC $csvSettingsData.CC -SMTPServer $csvSettingsData.SMTPServer -SMTPPort $csvSettingsData.SMTPPort | |
} | |
If ($csvSettingsData.SendMail -eq 'YES' -and $Global:ErrorResult) { | |
# Write to log | |
Write-Log 'CSV Data Processing - Failed!' | |
# Write to log | |
Write-Log -Message "Sending Error Mail Report..." | |
# Sending mail | |
Send-Mail -Subject 'Warning: Adding Device Direct Membership Rules to Collection - Failed!' -Body "Errors: `n $Global:ErrorResult" -From $csvSettingsData.From -To $csvSettingsData.To -CC $csvSettingsData.CC -SMTPServer $csvSettingsData.SMTPServer -SMTPPort $csvSettingsData.SMTPPort | |
} | |
} | |
} | |
#endregion | |
#region Function Test-FileChangeEvent | |
Function Test-FileChangeEvent { | |
<# | |
.SYNOPSIS | |
Workaround for FileSystemWatcher firing multiple events during a write operation. | |
.DESCRIPTION | |
FileSystemWatcher may fire multiple events on a write operation. | |
It's a known problem but it's not a bug in FileSystemWatcher. | |
This function is discarding events fired more than once a second. | |
.PARAMETER $WatchFilePath | |
Specify file path to be watched. | |
.PARAMETER $LastWriteTimeReference | |
Specify file last read time for reference. | |
.EXAMPLE | |
Test-FileChangeEvent -WatchFilePath $WatchFilePath -LastWriteTimeReference $LastWriteTimeReference | |
.NOTES | |
This is an internal script function and should typically not be called directly. | |
.LINK | |
https://SCCM.Zone | |
.LINK | |
https://SCCM.Zone/Git | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true)] | |
[Alias('WatchPath')] | |
[String]$WatchFilePath, | |
[Parameter(Mandatory=$true)] | |
[Alias('WriteTimeReference')] | |
[DateTime]$LastWriteTimeReference | |
) | |
## Get file last write time | |
Try { | |
[DateTime]$FileLastWriteTime = (Get-ItemProperty -Path $WatchFilePath).LastWriteTime | |
} | |
Catch { | |
# write to log | |
Write-Log -Message "Reading Last Write Time from $WatchFilePath - Failed!" | |
} | |
## Test if the file change event is valid by comparing the file last write time and reference parameter specified time | |
If (($FileLastWriteTime - $LastWriteTimeReference).Seconds -ge 1) { | |
## Write to log | |
Write-Log -Message "`nFile change - Detected!" -SkipEventLog | |
## Start main data processing and wait for it to finish | |
Start-DataProcessing | Out-Null | |
} | |
Else { | |
## Do nothing, the file change event was fired more than once a second | |
} | |
} | |
#endregion | |
#endregion | |
##*============================================= | |
##* END FUNCTION LISTINGS | |
##*============================================= | |
##*============================================= | |
##* SCRIPT BODY | |
##*============================================= | |
#region ScriptBody | |
## Initialize file watcher and wait for file changes | |
$FileWatcher = New-Object System.IO.FileSystemWatcher | |
$FileWatcher.Path = $ScriptPath | |
$FileWatcher.Filter = $csvDataFileNameWithExtension | |
$FileWatcher.IncludeSubdirectories = $false | |
$FileWatcher.NotifyFilter = [System.IO.NotifyFilters]::'LastWrite' | |
$FileWatcher.EnableRaisingEvents = $True | |
# Register file watcher event | |
Register-ObjectEvent -InputObject $FileWatcher -EventName 'Changed' -Action { | |
# Test if we really need to start processing | |
Test-FileChangeEvent -LastWriteTimeReference $LastWriteTimeReference -WatchFilePath $csvDataFilePath | |
# Reinitialize DateTime variable to be used on next file change event | |
$LastWriteTimeReference = (Get-Date) | |
} | |
#endregion | |
##*============================================= | |
##* END SCRIPT BODY | |
##*============================================= |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment