All scripts referenced in other pages within the documentation.

On this page:

Scripts for installation and upgrades

Install Windows Server roles and features

The following PowerShell script is used on Preparation: Windows Server roles and features.

Click here to download - RolesInstall.ps1

RolesInstall.ps1 - Configure IIS using PowerShell
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

Change Shopping 6.0 Sync from ActiveEfficiency to direct Configuration Manager

The following SQL script is used on Upgrading to Tachyon Platform: In-place upgrade of ActiveEfficiency Server for Shopping.

Click here to download - ReplaceActiveEfficiencySyncWithConfigMgrSync.sql

 ReplaceActiveEfficiencySyncWithConfigMgrSync.sql Expand source...

-- 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 Upgrading to Tachyon Platform: Side-by-side upgrade of ActiveEfficiency Server for Nomad.

Click here to download - AE-CD Data Migration Scripts.zip

  1. ExportData.ps1 - run on any computer that has read access to the ActiveEfficiency database
  2. CreateTables.sql - run on the SQL Server instance that hosts the ContentDistribution database created by Tachyon Setup
  3. 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:

Click here to download - ConfigMgr_DViewAccess_permissions.SQL

ConfigMgr_DViewAccess_permissions.SQL
-- 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 Configuration Manager database before Tachyon is installed.

SCCM database query
/*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-Data database query
/*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 Cloud synchronization behavior post-installation

The following script is used to configure the 1E Catalog SubscriptionType setting, and is used on the Configuring 1E Catalog connection to 1E Cloud page.

SQL script to configure SubscriptionType 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

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.

SQL script to configure EnableAIPackageSync 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 following 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.

SQL script to configure AIModelDeploymentPath 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

Operational scripts

Create Direct-based management groups from a file

The following PowerShell script is used on Management Groups page.

Click here to download - Create-DirectManagementGroupUsingFile.ps1

Create-DirectManagementGroupUsingFile.ps1
<#
.SYNOPSIS
1E Tachyon – Create Direct Management Group
Copyright © 1e Ltd 2021 - all rights reserved
http://www.1e.com

Version 1.5

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 = "1.5" # 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)

    $payload = ConvertTo-Json $fqdnList

    $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/$MGName/$MgDescription"

    $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.

Click here to download - Create-DirectManagementGroupUsingCmCollection.ps1

Create-DirectManagementGroupUsingCmCollection.ps1
<#
.SYNOPSIS
1E Tachyon – Create Direct Management Group
Copyright © 1e Ltd 2021 - all rights reserved
http://www.1e.com

Version 1.5

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 = "1.5" # 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)

    $payload = ConvertTo-Json $fqdnList

    $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/$MGName/$MgDescription"

    $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