Scripts
All scripts are referenced on other pages within the documentation.
Scripts for installation and upgrades
Install Windows Server roles and features
The following PowerShell script is used on Windows Server roles and features. Tachyon Setup can do all of this except for Basic Authentication (required by 1E Core) and HTTP redirect (required if Nomad is supporting down-level clients after replacing ActiveEfficiency).
Import-Module ServerManager Get-WindowsFeature | Out-file $PSScriptRoot\ServerManager-1.txt -Append Install-WindowsFeature Web-Server, Web-Http-Redirect, Web-Dyn-Compression, Web-Basic-Auth, Web-IP-Security, Web-Windows-Auth, Web-Asp-Net45, Web-Mgmt-Console, Net-Framework-45-Core, Net-Framework-45-ASPNET Uninstall-WindowsFeature Web-DAV-Publishing Get-WindowsFeature | Out-file $PSScriptRoot\ServerManager-2.txt -Append
Changing the configuration of Network Adapters
The following extract is used here: Changing the configuration of Network Adapters.
If the configuration of any network adapters is changed, then the Tachyon website configuration for IP Address and Domain Restrictions may need to be updated with the server's new IPv4 and IPv6 addresses. Failure to update the configuration after a network change will cause issues between the Switch and the Core and prevent the Tachyon Server from functioning.
Use the PowerShell cmdlet Gevt-NetIPAddress to determine the current IP Addresses, and if necessary update the configuration of the Core web application.
The following AddIpSec.ps1 script can be used to refresh local IP Addresses. It does not remove old, unwanted, or remote IP Addresses. Nor does it add addresses for remote Switches, which would need to be added manually.
# Adds local IPv4, v6 and loopback addresses to the IP Address and Domain Restrictions of the specified web applications. It does not delete any addresses # Remote addresses must be manually configured. # It only adds addresses if the specified web application exists. Import-Module "WebAdministration" Function AddIpSec() { param( [Parameter(Mandatory=$true)][string]$SiteName, [Parameter(Mandatory=$true)][string]$AppName ) Write-Output "Adding IP restrictions to $AppName" $fullAppPath = "IIS:\Sites\" + $SiteName + "\" + $AppName if (Test-Path -Path $fullAppPath) { $appPath = $SiteName + "/" + $AppName $ipAddreses = Get-NetIPAddress | sort IpAddress | foreach { $_.IPAddress } Set-WebConfigurationProperty /system.webserver/security/ipsecurity -Name allowUnlisted -Value "false" -Location $appPath -PSPath IIS:\ foreach ($ipAddress in $ipAddreses) { $existingRestriction = Get-WebConfiguration system.webServer/Security/ipSecurity/add -Location $appPath -PSPath IIS:\ | where { $_.ipAddress -eq $ipAddress } if (-not ($existingRestriction)) { Add-WebConfiguration system.webServer/security/ipSecurity -Location $appPath -Value @{ipAddress="$ipAddress";allowed="true"} -PSPath IIS:\ Write-Output "Allowed restriction on IP $ipAddress added to $AppName" } else { Write-Output "Restriction on IP $ipAddress already exists in $AppName. So skipping." } } } else { Write-Output "$AppName web application does not exist" } } $siteName = "Tachyon" AddIpSec -SiteName $siteName -AppName "Core" AddIpSec -SiteName $siteName -AppName "WelcomeCore"
Change Shopping 6.0 Sync from ActiveEfficiency to direct Configuration Manager
The following SQL script is used on In-place upgrade of ActiveEfficiency Server for Nomad.
-- Disclaimer: -- Your use of this script is at your sole risk. This script is provided "as-is", without any warranty, -- whether express or implied, of accuracy, completeness, fitness for a particular purpose, title or -- non-infringement, and is not supported or guaranteed by 1E. 1E shall not be liable for any damages -- you may sustain by using this script, whether direct, indirect, special, incidental or consequential, -- even if it has been advised of the possibility of such damages. UPDATE dbo.tb_Preference SET Description = 'Sets the interval for synchronizing the members in Computer Category groups held in the Shopping database with Active Directory. Also used for the ConfigMgr, Intune Machine sync and the ConfigMgr, Intune user sync. The value for the interval uses the units defined in the Active Directory Sync Units parameter.' WHERE preferencename = 'Users and Machines, AD Sync Interval' GO UPDATE dbo.tb_Preference SET Description = 'The unit is used by the AD, ConfigMgr, Intune Machine and User Sync Interval setting. This may be set to one of the following values: Days, Hours or Minutes.' WHERE preferencename = 'Users and Machines, AD Sync Units' GO UPDATE dbo.tb_Preference SET PreferenceValue = 'False' WHERE preferencename = 'Sync Machines and Users From Active Efficiency' GO UPDATE dbo.tb_Preference SET Hidden = 1 WHERE preferencename = 'Sync Machines From Old Sccm' GO UPDATE dbo.tb_Preference SET Hidden = 1 WHERE preferencename = 'ActiveEfficiency ServerName' GO
Manually migrate Nomad data from ActiveEfficiency to Content Distribution
The following PowerShell and SQL scripts are used on Side-by-side upgrade of ActiveEfficiency Server for Nomad. Click to download - AE-CD Data Migration Scripts.zip
Note
ExportData.ps1 - run on any computer that has read access to the ActiveEfficiency database
CreateTables.sql - run on the SQL Server instance that hosts the ContentDistribution database created by Tachyon Setup
ImportData.ps1 - run on any computer that has write access to the ContentDistribution database.
Granting Network Service access to Configuration Manager
The following SQL script is used on System Center Configuration Manager connector configuration.
-- Connect to the SQL instance using an account that has sysadmin rights. -- Execute this SQL script on the Configuration Manager database. -- The script creates a SQL Login on the instance for [hostname\ConfigMgr_DViewAccess] -- and grants it the following rights on the Configuration Manager database: -- [db_datareader] -- EXECUTE on [dbo].[fn_GetAppState] -- EXECUTE on [dbo].[fnGetSiteNumber] DECLARE @SqlLogin nvarchar(2000) DECLARE @SqlString nvarchar(max) SET @SqlLogin = CAST(host_name() AS varchar(512)) + '\ConfigMgr_DViewAccess' PRINT 'Granting permissions for ''' + @SqlLogin + ''' on database ' + DB_NAME() + ' ...' SET @SqlString = ' IF NOT EXISTS ( SELECT schema_name FROM information_schema.schemata WHERE schema_name = '''+@SqlLogin +''' ) EXEC (''CREATE SCHEMA [' + @SqlLogin + ']'') ' EXEC sp_executesql @SqlString SET @SqlString = ' IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = '''+ @SqlLogin +''') CREATE LOGIN [' + @SqlLogin + '] FROM WINDOWS WITH DEFAULT_DATABASE=[master] ' EXEC sp_executesql @SqlString SET @SqlString = ' IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = '''+ @SqlLogin +''') CREATE USER [' + @SqlLogin + '] FOR LOGIN [' + @SqlLogin + '] WITH DEFAULT_SCHEMA=[' + @SqlLogin + '] ' EXEC sp_executesql @SqlString PRINT 'SQL Login created.' SET @SqlString = ' ALTER ROLE [db_datareader] ADD MEMBER [' + @SqlLogin + '] GRANT EXECUTE ON [dbo].[fn_GetAppState] TO [' + @SqlLogin + '] AS [dbo] GRANT EXECUTE ON [dbo].[fnGetSiteNumber] TO [' + @SqlLogin + '] AS [dbo] ' EXEC sp_executesql @SqlString PRINT 'Permissions granted.'
Estimating the RAM requirements for AI Powered Auto-curation
The following scripts are used on AI Powered Auto-curation to calculate the amount of additional RAM that is required before you enable this feature.
The following script is run on the Configuration Manager database before Tachyon is installed.
/*SCCM database query to get number of distinct software titles from SCCM database*/ USE [YourSCCMDBName] GO SELECT COUNT(*) AS 'Distinct Software Titles' FROM ( SELECT DISTINCT DisplayName0 AS Title FROM [v_ADD_REMOVE_PROGRAMS] WITH (NOLOCK) UNION SELECT DISTINCT [Caption0] AS Title FROM [v_GS_OPERATING_SYSTEM] WITH (NOLOCK) ) AS Titles
The following script is run on SLA-Data database after Tachyon is installed and has already populated the inventory repository.
/*SLA query to get number of distinct software titles from SLA-Data database*/ USE [SLA-Data] CREATE TABLE #Software ( [SoftwareID] INT IDENTITY(1,1) PRIMARY KEY, [DataSource] NVARCHAR(255) COLLATE DATABASE_DEFAULT, [SoftwareIdent] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [SoftwareIdent_Hash] VARBINARY(128), [Vendor] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [Vendor_Hash] VARBINARY(128), [Title] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [Title_Hash] VARBINARY(128), [Version] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [Version_Hash] VARBINARY(128), [ColloquialVersion] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [ColloquialVersion_Hash] VARBINARY(128), [Edition] NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, [Edition_Hash] VARBINARY(128), [NormalizedProductID] INT ); SELECT * FROM #Software EXEC [usp_ReportDataEx_se] 1, N'Software', N'#Software'; SELECT count(*) AS 'Distinct Software Titles' FROM ( SELECT DISTINCT [Title] FROM #Software ) a DROP TABLE #Software
Modifying 1E Catalog AI Powered Auto-curation file download post-installation
The following script can be used to modify the EnableAIPackageSync setting in the Settings table of the 1ECatalog database, and is used on the Configuring 1E Catalog connection to 1E Cloud page. Click below to view the SQL script to configure the this setting.
/* Script to change 1ECatalog setting */ USE [1ECatalog] GO DECLARE @setting nvarchar(max), @oldvalue nvarchar(max), @newvalue nvarchar(max);; SET @setting = 'EnableAIPackageSync' SET @newvalue = 'True' SET @oldvalue = (SELECT [Value] FROM [dbo].[Settings] WHERE [Key]= @setting) UPDATE [dbo].[Settings] SET [Value]=@newvalue WHERE [Key]=@setting SELECT @setting AS 'Setting', @oldvalue AS 'Before', [Value] AS 'After' FROM [dbo].[Settings] WHERE [Key]=@setting GO
Modifying the AI Package Deployment Path
The Script to change SLA Deployment Path for AI Package
script can be used to modify the AIModelDeploymentPath setting in the Settings tables of the SLA-Shared and SLA-Data databases and is used on the AI Powered Auto-curation page. It is not possible to modify the download path use by the 1E Catalog Update service. Click below to view the SQL script to configure this setting.
/* Script to change SLA Deployment Path for AI Package */ /* The value is copied from SLA-Shared to instance in SLA-Data when a new inventory repository is created */ /* Updating SLA-Shared means script is not required for each new repository */ /* Updating all values in SLA-Data means all repositories use the same value */ DECLARE @setting nvarchar(max), @oldvalue nvarchar(max), @newvalue nvarchar(max) SELECT @newvalue = 'C:\ProgramData\1E\SLA Platform\AI' SELECT @setting = 'AIModelDeploymentPath' USE [SLA-Shared] SELECT @oldvalue = [VALUE] FROM [dbo].[ProjectSetting] WHERE [Group] = 'ProcessAIEngine' AND [Name] = @setting UPDATE [dbo].[ProjectSetting] SET [Value]=@newvalue WHERE [Name]=@setting SELECT DB_NAME() + '.dbo.ProjectSetting' AS 'Table', @setting AS 'Setting', @oldvalue AS 'Before', [Value] AS 'After' FROM [dbo].[ProjectSetting] WHERE [Name]=@setting USE [SLA-Data] SELECT @oldvalue = [VALUE] FROM [dbo].InstanceSetting WHERE [Group] = 'ProcessAIEngine' AND [Name] = @setting AND [InstanceID] = 1 UPDATE [dbo].[InstanceSetting] SET [Value]=@newvalue WHERE [Name]=@setting SELECT DISTINCT DB_NAME() + '.dbo.InstanceSetting' AS 'Table', @setting AS 'Setting', @oldvalue AS 'Before', [Value] AS 'After' FROM [dbo].[InstanceSetting] WHERE [Name]=@setting GO
Modifying 1E Client Cloud synchronization behavior post-installation
The Script to change 1ECatalog setting
script is used to configure the 1E Catalog SubscriptionType setting and is used on the Configuring 1E Catalog connection to 1E Cloud page. Click below to view the SQL script to configure this setting.
/* Script to change 1ECatalog setting */ USE [1ECatalog] GO DECLARE @setting nvarchar(max), @oldvalue nvarchar(max), @newvalue nvarchar(max);; SET @setting = 'SubscriptionType' SET @newvalue = '2' SET @oldvalue = (SELECT [Value] FROM [dbo].[Settings] WHERE [Key]= @setting) UPDATE [dbo].[Settings] SET [Value]=@newvalue WHERE [Key]=@setting SELECT @setting AS 'Setting', @oldvalue AS 'Before', [Value] AS 'After' FROM [dbo].[Settings] WHERE [Key]=@setting GO
Operational scripts
Create Direct-based management groups from a file
The following PowerShell script is used on Management Groups page.
Note
This script has changed to support an API change in Platform 8.1
Click here to download - Create-DirectManagementGroupUsingFile.ps1
Click Read more... to view Create-DirectManagementGroupUsingFile.ps1.
<# .SYNOPSIS 1E Tachyon – Create Direct Management Group Copyright © 1e Ltd 2022 - all rights reserved http://www.1e.com Version 2.0 Disclaimer Your use of this software is at your sole risk. All software is provided "as-is", without any warranty, whether express or implied, of accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the software is supported or guaranteed by 1E. 1E shall not be liable for any damages you may sustain by using this software, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages. If you have an issue using this software, please ensure you are using an unmodified version before contacting 1E Support. .DESCRIPTION This script reads the Device FQDN list in files and creates Direct Membership Management Groups through SLA/Tachyon APIs. If the group does not exist it is created and devices are added. If the group exists, the existing devices are deleted and new devices are added. .PARAMETER TachyonApiBaseUrl Mandatory parameter. The header name used to access the Tachyon web service. Should match the header listed in the IIS bindings on the Tachyon server. .PARAMETER FolderPath Mandatory parameter. The name of the input folder which contains the files to be used in creating Direct Membership Management Groups. These files contain the list of devices to be used in creating Management Groups. The File name should be same as the name of Name of Management Group. Also, the first line of the file should contain the description of Management Group to be created, from next line onwards every line contains the Device FQDN. .PARAMETER UseCustomCredentials Optional switch. If UseCustomCredentials is used, User will be prompted to enter the credentials, otherwise the script will use default credentials. .EXAMPLE .\Create-DirectManagementGroupUsingFile.ps1 -TachyonApiBaseUrl https://tachyon.lab.local -FolderPath C:\Temp\MgFolder Scenario: A Tachyon server is accessed using URL https://tachyon.lab.local/Tachyon using default credentials. The files containing the Device FQDN list is in the directory C:\Temp\MyFolder. .EXAMPLE ..\Create-DirectManagementGroupUsingFile.ps1 -TachyonApiBaseUrl https://tachyon.lab.local -FolderPath C:\Temp\MgFolder -UseCustomCredentials Scenario: A Tachyon server is accessed using URL https://tachyon.lab.local/Tachyon using different credentials other than current default credentials. The files containing the Device FQDN list is in the directory C:\Temp\MyFolder. #> param( [Parameter(Mandatory=$true, HelpMessage="Enter Tachyon API Base URL e.g. : https://tachyon.testlab.com")] [ValidateNotNullOrEmpty()] [string] $TachyonApiBaseUrl, [Parameter(Mandatory=$true, HelpMessage="Enter Folder Path which contains files containing Device FQDN list")] [ValidateNotNullOrEmpty()] [string] $FolderPath, [Parameter(Mandatory=$false, HelpMessage="To use Custom Credentials other than windows default credentials")] [Switch] $UseCustomCredentials ) $ScriptVersion = "2.0" # Update the script version here function Write-Log { [CmdletBinding()] Param( [parameter(Mandatory=$true)] [String]$Path, [parameter(Mandatory=$true)] [String]$Message, [parameter(Mandatory=$true)] [String]$Component, [Parameter(Mandatory=$true)] [ValidateSet("Debug", "Info", "Message", "Warning", "Error")] [String]$Type ) switch ($Type) { "Info" { [int]$Type = 1 } "Warning" { [int]$Type = 2 } "Error" { [int]$Type = 3 } "Debug" { [int]$Type = 4 } "Message" { [int]$Type = 5 } } # 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=`"`">" try { # Write the line to the log file Add-Content -Path $Path -Value $Content } catch { Write-Host "User does not have access to write log at path $Path" Write-Host $Message } if ($Type -eq 1) { Write-Host $Message -ForegroundColor Yellow } if ($Type -eq 3) { Write-Host $Message -ForegroundColor Red } if ($Type -eq 5) { Write-Host $Message } } function Invoke-DirectMgApi{ [CmdletBinding()] Param( [parameter(Mandatory=$true)] [String]$uri, [parameter(Mandatory=$true)] [String]$payload, [parameter(Mandatory=$false)] [System.Management.Automation.PSCredential]$credential ) $hdrs = @{ "Accept" = "application/json" "Content-type" = "application/json" "X-Tachyon-Consumer" = "Platform" } try { $stopwatchApi = [system.diagnostics.stopwatch]::StartNew() $Content = "Invoking API" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 if($credential){ $response = Invoke-RestMethod -Method POST -Uri $uri -Body $payload -Credential $credential -Headers $hdrs $Content = "Response from API : $response" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } else { $response = Invoke-RestMethod -Method POST -Uri $uri -Body $payload -UseDefaultCredentials -Headers $hdrs $Content = "Response from API : $response" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } $stopwatchApi.Stop() $Content = "Time taken by API : $($stopwatchApi.Elapsed.TotalSeconds) seconds" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } catch { $err = $_ $Content = "An error occured : $($err.Exception)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error $Content = "Error Details : $($err)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug } } # Get the Current Directory $CurrentDirectory = $PSScriptRoot if ($UseCustomCredentials -eq $true) { $cred = Get-Credential } $LogFilePath = Join-Path $PSScriptRoot "$($MyInvocation.MyCommand.Name)_$(Get-Date -Format ddMMyyyyHHmmss).log" $Content = "************************************************" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Starting script" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "************************************************" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Running Script Version: " + $ScriptVersion Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug # Get the Current Directory $Content = "Current Directory is " + $CurrentDirectory Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Tachyon API Base URL: " + $TachyonApiBaseUrl Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = "Folder Path containing Device list files: " + $FolderPath Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = "Verifying Folder Path : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug if (!(Test-Path $FolderPath)) { $Content = "Folder path does not exist : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } $Content = "Fetching all supported files from folder" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $files = Get-ChildItem -Path $FolderPath -Filter *.txt $Content = "Total Files in folder $FolderPath : $($files.Count)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug foreach($file in $files) { $fileFullPath = $file.FullName $Content = "Reading File : $fileFullPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $stopwatchFileRead = [system.diagnostics.stopwatch]::StartNew() $fqdnList = New-Object System.Collections.Generic.List[System.Object] foreach($line in [System.IO.File]::ReadLines($fileFullPath)) { if($line -ne ""){ $fqdnList.Add($line.Trim()) } } if($fqdnList.Count -lt 2){ $Content = "No device present in file" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error Continue } #File name is Management Group Name $MGName = $file.BaseName #First line of the file is the Management Group Description $MgDescription = $fqdnList[0] #Removing the first line from collection as it's MG description $fqdnList.RemoveAt(0) $uploadModelObject = [PSCustomObject]@{ Name = $MGName Description = $MgDescription Devices = $fqdnList } $payload = ConvertTo-Json $uploadModelObject $stopwatchFileRead.Stop() $Content = "Time taken in reading file : $($stopwatchFileRead.Elapsed.TotalSeconds) seconds" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Creating Management Group -" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Info $Content = " Name : $MGName" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = " Description : $MgDescription" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = " Total Devices in file : $($fqdnList.Count)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message #trimming any white space if any in the begining or end in url $TachyonApiBaseUrl = $TachyonApiBaseUrl.Trim() $uri = "$TachyonApiBaseUrl/admin/managementgroups/upload" $Content = "URI : $uri" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug Invoke-DirectMgApi -uri $uri -payload $payload -Credential $cred } $Content = "Complete" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message
Create Direct-based management groups from a Configuration Manager collection
The following PowerShell script is used on Management Groups page.
Note
This script has changed to support an API change in Platform 8.1
Click here to download - Create-DirectManagementGroupUsingCmCollection.ps1
Click Read more... to view Create-DirectManagementGroupUsingCmCollection.ps1.
<# .SYNOPSIS 1E Tachyon – Create Direct Management Group Copyright © 1e Ltd 2022 - all rights reserved http://www.1e.com Version 2.0 Disclaimer Your use of this software is at your sole risk. All software is provided "as-is", without any warranty, whether express or implied, of accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the software is supported or guaranteed by 1E. 1E shall not be liable for any damages you may sustain by using this software, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages. If you have an issue using this software, please ensure you are using an unmodified version before contacting 1E Support. .DESCRIPTION This script reads the memberships of ConfigMgr Collections (Both Direct and Query based membership) and creates Direct Membership Management Groups through SLA/Tachyon APIs. If the group does not exist it is created, and devices are added. If the group exists, the existing devices are deleted and new devices are added. The script is designed to run from the ConfigMgr server It queries WMI and gets the information necessary to connect to the ConfigMgr's site namespace. .PARAMETER TachyonApiBaseUrl Mandatory parameter. The header name used to access the Tachyon web service. Should match the header listed in the IIS bindings on the Tachyon server. .PARAMETER FilePath Mandatory parameter. The name of the input file which contains the list of collections to be used in creating Direct Membership Management Groups. Each line in file contains Collection Name, Management Group Name, and Management Group Description; all three values separated by a comma. e.g. Collection 1,Management Group 1, Management Group 1 Description Collection 2,Management Group 2, Management Group 2 Description .PARAMETER UseCustomCredentials Optional switch. If UseCustomCredentials is used, User will be prompted to enter the credentials, otherwise the script will use default credentials. .EXAMPLE .\Create-DirectManagementGroupUsingCmCollection.ps1 -TachyonApiBaseUrl https://tachyon.lab.local -FilePath C:\Temp\Collection.txt Scenario: A Tachyon server is accessed using URL https://tachyon.lab.local/Tachyon using default credentials. The file containing the list of collections to process is in the location C:\Temp\Collection.txt. .EXAMPLE ..\Create-DirectManagementGroupUsingCmCollection.ps1 -TachyonApiBaseUrl https://tachyon.lab.local -FilePath C:\Temp\Collection.txt -UseCustomCredentials Scenario: A Tachyon server is accessed using URL https://tachyon.lab.local/Tachyon using different credentials other than current default credentials. The file containing the list of collections to process is in the location C:\Temp\Collection.txt. #> param( [Parameter(Mandatory=$true, HelpMessage="Enter Tachyon API Base URL e.g. : https://tachyon.testlab.com")] [ValidateNotNullOrEmpty()] [string] $TachyonApiBaseUrl, [Parameter(Mandatory=$true, HelpMessage="Enter File Path which contains collection details in csv format. The file should contain Collection Name, Management Group Name, and Management Group Description")] [ValidateNotNullOrEmpty()] [string] $FilePath, [Parameter(Mandatory=$false, HelpMessage="To use Custom Credentials other than windows default credentials")] [Switch] $UseCustomCredentials ) $ScriptVersion = "2.0" # Update the script version here function Write-log { [CmdletBinding()] Param( [parameter(Mandatory=$true)] [String]$Path, [parameter(Mandatory=$true)] [String]$Message, [parameter(Mandatory=$true)] [String]$Component, [Parameter(Mandatory=$true)] [ValidateSet("Debug", "Info", "Message", "Warning", "Error")] [String]$Type ) switch ($Type) { "Info" { [int]$Type = 1 } "Warning" { [int]$Type = 2 } "Error" { [int]$Type = 3 } "Debug" { [int]$Type = 4 } "Message" { [int]$Type = 5 } } # 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=`"`">" try { # Write the line to the log file Add-Content -Path $Path -Value $Content } catch { Write-Host "User does not have access to write log at path $Path" -ForegroundColor Yellow Write-Host $Message } if ($Type -eq 1) { Write-Host $Message -ForegroundColor Yellow } if ($Type -eq 3) { Write-Host $Message -ForegroundColor Red } if ($Type -eq 5) { Write-Host $Message } } function Invoke-DirectMgApi{ [CmdletBinding()] Param( [parameter(Mandatory=$true)] [String]$uri, [parameter(Mandatory=$true)] [String]$payload, [parameter(Mandatory=$false)] [System.Management.Automation.PSCredential]$credential ) $hdrs = @{ "Accept" = "application/json" "Content-type" = "application/json" "X-Tachyon-Consumer" = "Platform" } try { $stopwatchApi = [system.diagnostics.stopwatch]::StartNew() $Content = "Invoking API" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 if($credential){ $response = Invoke-RestMethod -Method POST -Uri $uri -Body $payload -Credential $credential -Headers $hdrs $Content = "Response from API : $response" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } else { $response = Invoke-RestMethod -Method POST -Uri $uri -Body $payload -UseDefaultCredentials -Headers $hdrs $Content = "Response from API : $response" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } $stopwatchApi.Stop() $Content = "Time taken by API : $($stopwatchApi.Elapsed.TotalSeconds) seconds" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } catch { $err = $_ $Content = "An error occured : $($err.Exception)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error $Content = "Error Details : $($err)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug } } function Connect-ConfigMgrModule{ try { # Load CM PowerShell Module if (-Not (Test-Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\ConfigMgr10\AdminUI")) { $Content = "Configuration Manager not found. Pleaes verify if the machine has Configuration Manager installed." Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } $CMConsolePath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\ConfigMgr10\AdminUI" | Select-Object -expandProperty AdminUILog).Replace("\AdminUILog\", "") if ($CMConsolePath) { Import-Module -Name "$CMConsolePath\bin\ConfigurationManager.psd1" $Content = "Loaded ConfigurationManager.psd1 module" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug } else { $Content = "ConfigurationManager.psd1 module not found. Pleaes verify if the machine has Configuration Manager installed." Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } # Get the CM Site Code $CMSiteCode = Get-WMIObject -Namespace root\sms -Class SMS_ProviderLocation | Select-Object -expandproperty SiteCode if ($CMSiteCode -eq "") { $Content = "Sitecode could not be determined." Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } $Content = "ConfigMgr Site Code: " + $CMSiteCode Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $CMLocation = $CMSiteCode + ":" $Content = "Setting Location : $CMLocation" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug Push-Location Set-Location -Path $CMLocation } catch { $err = $_ $Content = "An error occured while loading configuration manager module : $($err.Exception)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error $Content = "Error Details : $($err)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug exit } } function Read-ConfigMgrDeviceCollection{ [CmdletBinding()] Param( [parameter(Mandatory=$true)] [hashtable]$collectionList ) try { $stopwatch = [system.diagnostics.stopwatch]::StartNew() $arrCMCollList = Get-CMDeviceCollection | Select-Object -Property CollectionID,Name ForEach ($CMCollection IN $arrCMCollList){ $CMCollName = $CMCollection.Name $Content = "Retrieved Collection Name from ConfigMgr: " + $CMCollName Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $CMCollId = $CMCollection.CollectionID If ($collectionList.ContainsKey($CMCollName)){ $arrCollectionMembership = Get-CMCollectionMember -CollectionId $CMCollID | Select-Object -expandProperty Name If ($null -ne $arrCollectionMembership){ $Content = "Fetching Devices from Collection : " + $CMCollName Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $MgName = $collectionList["$CMCollName"][0] $MgDescription = $collectionList["$CMCollName"][1] #Create File to save the device list, which later will be used to create MG $MGFileName = $MgName + ".txt" $MGFilePath = Join-Path $FolderPath $MGFileName if (!(Test-Path $MGFilePath)){ $Content = New-Item -itemType File -Path $FolderPath -Name $MGFileName Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Created file $MGFilePath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug Add-Content -Path "$MGFilePath" -Value "$MgDescription" $Content = "Added description $MgDescription" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug } ForEach ($CollMember IN $arrCollectionMembership){ $deviceDetails = Get-CMDevice -Name $CollMember -Resource | Select-Object Name,FullDomainName $deviceFullName = $deviceDetails.Name + "." + $deviceDetails.FullDomainName $Content = "Adding device: " + $deviceFullName + " to MG File: " + $MgName Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug Add-Content -Path "$MGFilePath" -Value "$deviceFullName" } } } } $stopwatch.Stop() $Content = "Time taken in reading devices from Collection : $($stopwatch.Elapsed.TotalSeconds) seconds" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message Pop-Location $Content = "Update location back to local drive" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "**********************" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message } catch { $err = $_ $Content = "An error occured while fetching devices : $($err.Exception)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error $Content = "Error Details : $($err)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug exit } } # Get the Current Directory $CurrentDirectory = $PSScriptRoot #Temp Folder to contain list of devices which will be used to create MG $MGFolderName = "MGDeviceList" $FolderPath = Join-Path $CurrentDirectory $MGFolderName if ($UseCustomCredentials -eq $true) { $cred = Get-Credential } $LogFilePath = Join-Path $CurrentDirectory "$($MyInvocation.MyCommand.Name)_$(Get-Date -Format ddMMyyyyHHmmss).log" $Content = "************************************************" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Starting Script" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "************************************************" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Running Script Version: " + $ScriptVersion Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Current Directory is: " + $CurrentDirectory Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Tachyon API Base URL: " + $TachyonApiBaseUrl Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = "File Path containing CM Collection list: " + $FilePath Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = "Temp Folder Path to save Device List: " + $FolderPath Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = "Verifying File Path : $FilePath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug if (!(Test-Path $FilePath)) { $Content = "File path does not exist : $FilePath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } try { if (!(Test-Path $FolderPath)) { $Content = "Creating temp folder : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = New-Item -Path "$CurrentDirectory" -Name $MGFolderName -ItemType "directory" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug } else{ $Content = "Deleting existing MG files from $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug # Delete Collection Membership List Files Remove-Item -Path "$FolderPath\*.*" } } catch { $Content = "User does not have permission to create : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } # Read Collection Details from File $Content = "Reading collection details from $FilePath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $collectionList = @{} foreach($line in [System.IO.File]::ReadLines($FilePath)) { if($line -ne ""){ $collectionDetails = $line.Split(",") if($collectionDetails.Count -lt 3){ $Content = "Input data not in correct format. It should contain <CollectionName>,<MGName>,<MGDescription>" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } $collectionList.Add($collectionDetails[0],@($collectionDetails[1],$collectionDetails[2])) } } if($collectionList.Count -lt 1){ $Content = "No collection details present in file" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } #Loading Config Manager Powershell Modules Connect-ConfigMgrModule #Read Device Collections from Config Manager and prepare device list in temp folder which will be used to create Read-ConfigMgrDeviceCollection -collectionList $collectionList $Content = "Verifying Folder Path : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug if (!(Test-Path $FolderPath)) { $Content = "Folder path does not exist : $FolderPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error exit } $Content = "Fetching all files from folder" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $files = Get-ChildItem -Path $FolderPath -Filter *.txt $Content = "Total Files in folder $FolderPath : $($files.Count)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug foreach($file in $files) { $fileFullPath = $file.FullName $Content = "Reading File : $fileFullPath" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $stopwatchFileRead = [system.diagnostics.stopwatch]::StartNew() $fqdnList = New-Object System.Collections.Generic.List[System.Object] foreach($line in [System.IO.File]::ReadLines($fileFullPath)) { if($line -ne ""){ $fqdnList.Add($line) } } if($fqdnList.Count -lt 2){ $Content = "No device present in file" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Error Continue } #File name is Management Group Name $MGName = $file.BaseName #First line of the file is the Management Group Description $MgDescription = $fqdnList[0] #Removing the first line from collection as it's MG description $fqdnList.RemoveAt(0) $uploadModelObject = [PSCustomObject]@{ Name = $MGName Description = $MgDescription Devices = $fqdnList } $payload = ConvertTo-Json $uploadModelObject $stopwatchFileRead.Stop() $Content = "Time taken in reading file : $($stopwatchFileRead.Elapsed.TotalSeconds) seconds" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug $Content = "Creating Management Group -" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Info $Content = " Name : $MGName" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = " Description : $MgDescription" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message $Content = " Total Devices in file : $($fqdnList.Count)" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message #trimming any white space if any in the begining or end in url $TachyonApiBaseUrl = $TachyonApiBaseUrl.Trim() $uri = "$TachyonApiBaseUrl/admin/managementgroups/upload" $Content = "URI : $uri" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Debug Invoke-DirectMgApi -uri $uri -payload $payload -Credential $cred } $Content = "Complete" Write-Log -Path $LogFilePath -Message $Content -Component $MyInvocation.MyCommand.Name -Type Message