Copy Site Settings between sites
This post is for a coworker of mine, Daniel. He’s a smart cookie I work with and has a great blog, PotentEngineer. Here ya go my friend!
Overview
I’ve been working on a PowerShell module for our environment that does a variety of things and I’ve been wanting to post it here on my blog. This request has kick started me on getting this done. I’ll be posting each function here and building the resulting module as I go, so to get started we need to take care of a couple of required functions in the module that are used in almost all the other functions.
Function New-CMNLogEntry
First, is a quick update to my earlier post for creating log entries. I’ve added some logic to check for maintaining the log size and rolling the logs. Since this is based of an earlier post, I’ll just mention what I’ve added.
- In order to keep my module commands unique, I’ve added ‘CMN’ to the beginning of the Noun part of the functions.
- I’ve added a maxLogSize parameter, that defaults to 5MB. This will roll the log over if the log exceeds this size.
Here’s the New-CMNLogEntry Function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
Function New-CMNLogEntry { <# .SYNOPSIS Writes log entry that can be read by CMTrace.exe .DESCRIPTION Writes log entries to a file. If the file is larger then MaxFileSize, it will rename it to *.lo_ and start a new file. You can specify if it's an informational, warning, or error message as well. It will also add time zone information, so if you have machines in multiple time zones, you can convert to UTC and make sure you know exactly when things happened. .PARAMETER entry This is the text that is the log entry. .PARAMETER type Defines the type of message, 1 = Informational (default), 2 = Warning, and 3 = Error. .PARAMETER component Specifies the Component information. This could be the name of the function, or thread, or whatever you like, to further help identify what is being logged. .PARAMETER logFile File for writing logs to. .PARAMETER maxLogSize Specifies, in bytes, how large the file should be before rolling log over. .EXAMPLE New-CMNLogEntry -entry "Machine $computerName needs a restart." -type 2 -component 'Installer' -logFile $logFile -MaxLogSize 10485760 This will add a warning entry, after expanding $computerName from the compontent Installer to the logfile and roll it over if it exceeds 10MB .LINK http://configman-notes.com .NOTES FileName: Copy-CMNApplicationDeployment.ps1 Author: James Parris Contact: jim@ConfigMan-Notes.com Created: 2016-03-22 Updated: 2017-03-01 Version: 1.0.2 Updated timezone to ensure all timezones work SMS_ApplicationLatest <- SMS_DeploymentType <- SMS_ObjectContentExtraInfo #> [CmdletBinding(ConfirmImpact = 'Low')] # Writes to the log file Param ( [Parameter(Mandatory = $true, HelpMessage = 'Entry for the log', Position = 0)] [String]$entry, [Parameter(Mandatory = $true, HelpMessage = 'Type of message, 1 = Informational, 2 = Warning, 3 = Error', Position = 1)] [ValidateSet(1, 2, 3)] [INT32]$type, [Parameter(Mandatory = $true, HelpMessage = 'Component', Position = 2)] [String]$component, [Parameter(Mandatory = $true, HelpMessage = 'Log File', Position = 4)] [String]$logFile='C:\Temp\Error.log', [Parameter(Mandatory = $false, HelpMessage = 'Max Log size', Position = 5)] [Int32]$MaxLogSize = 5242880 ) Write-Verbose $entry if ($entry.Length -eq 0) { $entry = 'N/A' } $tzOffset = ([TimeZoneInfo]::Local).BaseUTcOffset.TotalMinutes if($tzOffset -lt 0){$tzOffset = "$(Get-Date -Format "HH:mm:ss.fff")+$(-$tzOffset)"} else{$tzOffset = "$(Get-Date -Format "HH:mm:ss.fff")-$tzOffset"} $entry = "<![LOG[{0}]LOG]!><time=""{2}"" date=""{1}"" component=""{5}"" context="""" type=""{4}"" thread=""{3}"">" -f $entry, (Get-Date -Format "MM-dd-yyyy"), $tzOffset, $pid, $type, $component if(Test-Path $logFile) { if((Get-Item -Path $logFile).Length -gt $MaxLogSize) { $backupLog = $logFile -replace 'log$', 'lo_' if(Test-Path $backupLog){Remove-Item -Path $backupLog -Force} Rename-Item -Path $logFile -NewName $backupLog -Force } } $entry | Out-File $logFile -Append -Encoding ascii }#End New-CMNLogEntry |
Function Get-CMNSCCMConnectionInfo
Next, a majority of my functions need a PSObject that contains information for connecting to the SCCM Site Server and SQL Server, this function will return exactly what you need, you just need to provide the site servers name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
Function Get-CMNSCCMConnectionInfo { <# .SYNOPSIS Returns a hashtable to containing the SCCMDBServer (Site Database Server), SCCMDB (Site Database), ComputerName (Site Server), SiteCode (Site Code), and NameSpace (WMI NameSpace) .DESCRIPTION This function creates a hashtable with the necessary information used by a variety of my functions. Whenever a function has to talk to an SCCM site, it expects to be passed this hastable so it knows the connection information .PARAMETER SiteServer This is the siteserver for the site you want to connect to. .PARAMETER logFile File for writing logs to. .PARAMETER logEntries Switch to say whether or not to create a log file .PARAMETER maxLogSize Specifies, in bytes, how large the file should be before rolling log over. .EXAMPLE Get-CMNSccmConnctionInfo -SiteServer Server01 .LINK http://configman-notes.com .NOTES Author: James Parris Contact: jim@ConfigMan-Notes.com Created: 2016-11-07 Updated: Version: 1.0.0 #> param ( [Parameter(Mandatory = $true, HelpMessage="Site server where the SMS Provider is installed.", Position = 1)] [ValidateNotNullOrEmpty()] [ValidateScript({Test-Connection -ComputerName $_ -Count 1 -Quiet})] [string]$siteServer, [Parameter(Mandatory = $false, HelpMessage = 'LogFile Name', Position = 2)] [String]$logFile = 'C:\Temp\Error.log', [Parameter(Mandatory = $false, HelpMessage = 'Log entries', Position = 3)] [Switch]$logEntries = $false, [Parameter(Mandatory = $false, HelpMessage = 'Max Log size', Position = 4)] [Int32]$MaxLogSize = 5242880 ) Begin { #Build splat for log entries $NewLogEntry = @{ LogFile = $logFile; Component = 'Get-CMNSCCMConnectionInfo'; MaxLogSize = $MaxLogSize; } #Write to the log if we're supposed to! if($PSBoundParameters['logEntries']){New-CMNLogEntry -Entry 'Starting Function' -type 1 @NewLogEntry} } Process { #Get the site code from the site server $siteCode = $(Get-WmiObject -ComputerName $siteServer -Namespace 'root/SMS' -Class SMS_ProviderLocation -ErrorAction SilentlyContinue).SiteCode #if we don't get a result, we have a problem. if(-not($SiteCode)) { if($PSBoundParameters['logEntries']){New-CMNLogEntry -entry "Unable to connect to Site Server $SiteServer" -type 3 @NewLogEntry} throw "Unable to connect to Site Server $siteServer" break } #Now, to determine the SQL Server and database being used for the site. $DataSourceWMI = $(Get-WmiObject -Class SMS_SiteSystemSummarizer -Namespace root/sms/site_$siteCode -ComputerName $siteServer -Filter "Role = 'SMS SQL SERVER' and SiteCode = '$siteCode' and ObjectType = 1").SiteObject $SCCMDBServer = $DataSourceWMI -replace '.*\\\\([A-Z0-9_.]+)\\.*', '$+' $SCCMDB = $DataSourceWMI -replace ".*\\([A-Z_0-9]*?)\\$", '$+' #Now, we've got our data, time to return some results! $ReturnHashTable = @{ SCCMDBServer = $SCCMDBServer; SCCMDB = $SCCMDB; SiteCode = $SiteCode; ComputerName = $SiteServer; NameSpace = "Root/SMS/Site_$siteCode" } #Let's put our TypeName on the results $obj = New-Object -TypeName PSObject -Property $ReturnHashTable $obj.PSObject.TypeNames.Insert(0,'CMN.SCCMConnectionInfo') #Log if if we're supposed to! if($PSBoundParameters['logEntries']) { New-CMNLogEntry -entry "SCCMDBServer = $SCCMDBServer" -type 1 @NewLogEntry New-CMNLogEntry -entry "SCCMDB = $SCCMDB" -type 1 @NewLogEntry New-CMNLogEntry -entry "SiteCode = $siteCode" -type 1 @NewLogEntry New-CMNLogEntry -entry "ComputerName = $siteServer" -type 1 @NewLogEntry New-CMNLogEntry -entry "NameSpace = Root/SMS/Site_$siteCode" -type 1 @NewLogEntry } } End { #Done! Log it! if($PSBoundParameters['logEntries']){New-CMNLogEntry -Entry 'Completing Function' -Type 1 @NewLogEntry} Return $obj } } #End Get-CMNSCCMConnectionInfo |
Function Copy-CMNClientSettings
Now, it’s time for the actual script to copy the client settings!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
Function Copy-CMNClientSettings { <# .SYNOPSIS .DESCRIPTION .PARAMETER sourceConnection This is a connection object for the source site. The best way to get this is storing the results of Get-CMNSCCMConnectionInfo in a variable and passing that variable. .PARAMETER destinationConnection This is a connection object to the destination site. .PARAMETER logFile File for writing logs to .PARAMETER logEntries Switch to say whether or not to create a log file .PARAMETER clearLog If this is set, we will clear (delete) any existing log file. .PARAMETER maxLogSize Max size for the log. Defaults to 5MB. .EXAMPLE $SrcCon = Get-CMNSCCMConnectionInfo -SiteServer Server01 $DstCon = Get-CMNSCCMConnectionInfo -SiteServer Server02 Copy-CMNClientSettings -sourceConnection $SrcCon -destinationConnection $DstCon -logFile $logfile -logEntries .LINK http://configman-notes.com .NOTES FileName: Copy-CMNClientSettings.ps1 Author: James Parris Contact: jim@ConfigMan-Notes.com Created: 2016-03-22 Updated: 2017-03-2 Version: 1.0.1 #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] PARAM ( [Parameter(Mandatory = $true, HelpMessage = 'SCCM Connection Info', Position = 1)] [PSObject]$sourceConnection, [Parameter(Mandatory = $true, HelpMessage = 'Destination SCCM Connection', Position = 2)] [PSObject]$destinationConnection, [Parameter(Mandatory = $false, HelpMessage = 'LogFile Name', Position = 3)] [String]$logFile = 'C:\Temp\Error.log', [Parameter(Mandatory = $false, HelpMessage = 'Log entries', Position = 4)] [Switch]$logEntries, [Parameter(Mandatory = $false, HelpMessage = 'Clear Log File', Position = 5)] [Switch]$clearLog, [Parameter(Mandatory = $false, HelpMessage = 'Max Log size', Position = 6)] [Int32]$maxLogSize = 5242880 ) Begin { #Build splat for log entries $NewLogEntry = @{ logFile = $logFile; component = 'Copy-CMNClientSettings'; maxLogSize = $maxLogSize; } #Build splats for WMIQueries $WMISourceQueryParameters = @{ ComputerName = $sourceConnection.ComputerName; NameSpace = $sourceConnection.NameSpace; } $WMIDestinationQueryParameters = @{ ComputerName = $destinationConnection.ComputerName; NameSpace = $destinationConnection.NameSpace; } if($PSBoundParameters['clearLog']){if(Test-Path -Path $logFile){Remove-Item -Path $logFile}} if($PSBoundParameters['logEntries']){New-CMNLogEntry -entry 'Starting Function' -type 1 @NewLogEntry} } Process { if($PSBoundParameters['logEntries']){New-CMNLogEntry -entry 'Beginning processing loop' -type 1 @NewLogEntry} if($PSCmdlet.ShouldProcess($sourceConnection)) { #Let's get the settings from the source site. $query = 'SELECT * FROM SMS_ClientSettings order by priority' $clientSettings = Get-WmiObject -Query $query @WMISourceQueryParameters #Now to cycle through them and copy! foreach($clientSetting in $clientSettings) { #Get the individual setting $query = "select * from SMS_ClientSettings where Name = '$($clientSetting.Name)'" #Let's see if it already exists $testDestSettings = Get-WmiObject -Query $query @WMIDestinationQueryParameters if($testDestSettings) { Write-Output 'Already Exists' } else { #Eureka! Let's create it! First, get those lazy parameters.... $clientSetting.Get() #And we start anew! $destClientSettings = ([WMIClass]"//$($destinationConnection.ComputerName)/$($destinationConnection.NameSpace):SMS_ClientSettings").CreateInstance() #Now for those pesky details... $destClientSettings.AgentConfigurations = $clientSetting.AgentConfigurations $destClientSettings.Description = $clientSetting.Description $destClientSettings.Enabled = $clientSetting.Enabled $destClientSettings.FeatureType = $clientSetting.FeatureType $destClientSettings.Flags = $clientSetting.Flags $destClientSettings.Name = $clientSetting.Name $destClientSettings.Priority = $clientSetting.Priority $destClientSettings.Type = $clientSetting.Type #And save it! $destClientSettings.Put() } } } } End { if($PSBoundParameters['logEntries']){New-CMNLogEntry -entry 'Completing Function' -Type 1 @NewLogEntry} } } #End Copy-CMNClientSettings |
Finally
I’ll be continuing this series, adding more functions as I go. I’ll also be putting this in my bitBucket files, please let me know if you have any suggestions! Thank you!
Just what my organization needs!
Thank you sir! Glad it’s of use!