Initial commit with Autotask/DRMM
This commit is contained in:
129
InstaProvision.xaml
Normal file
129
InstaProvision.xaml
Normal file
@@ -0,0 +1,129 @@
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="InstaProvision - SVS Tools"
|
||||
Height="550" Width="480"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="#1e1e2f"
|
||||
ResizeMode="NoResize"
|
||||
FontFamily="Segoe UI">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- Rounded TextBox style -->
|
||||
<Style x:Key="RoundedTextBox" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="#2d2d3a"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="10,6"/>
|
||||
<Setter Property="BorderBrush" Value="#3f3f3f"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Height" Value="34"/>
|
||||
<Setter Property="Margin" Value="0,4,0,4"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border CornerRadius="10" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ScrollViewer x:Name="PART_ContentHost"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Fancy animated button -->
|
||||
<Style x:Key="FancyButton" TargetType="Button">
|
||||
<Setter Property="Background" Value="#007bff"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Padding" Value="8,4"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="buttonBorder" CornerRadius="8" Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#0056b3" Duration="0:0:0.2"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#007bff" Duration="0:0:0.2"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="buttonBorder" Property="Background" Value="#003f88"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Modern toggle-style CheckBox -->
|
||||
<Style x:Key="ModernCheckBox" TargetType="CheckBox">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Grid Width="40" Height="22" Margin="0,0,8,0">
|
||||
<Border x:Name="SwitchBorder" Background="#555" CornerRadius="11"/>
|
||||
<Ellipse x:Name="SwitchKnob" Fill="White" Width="18" Height="18" Margin="2" HorizontalAlignment="Left"/>
|
||||
</Grid>
|
||||
<ContentPresenter VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="SwitchBorder" Property="Background" Value="#28a745"/>
|
||||
<Setter TargetName="SwitchKnob" Property="HorizontalAlignment" Value="Right"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="20">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- Logo at top -->
|
||||
<Image Source="https://i.ibb.co/vCTJYn2j/svs-logo-nav-wh.png" Height="60" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Text="Provision Company" FontSize="20" FontWeight="SemiBold" Foreground="White" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
|
||||
<!-- Main Content -->
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="Company Name:" Margin="0,5,0,0" Foreground="#cccccc"/>
|
||||
<TextBox Name="CompanyNameBox" Style="{StaticResource RoundedTextBox}"/>
|
||||
|
||||
<TextBlock Name="PhoneLabel" Text="Phone Number:" Margin="0,10,0,0" Foreground="#cccccc"/>
|
||||
<TextBox Name="PhoneBox" Style="{StaticResource RoundedTextBox}"/>
|
||||
|
||||
<CheckBox Name="SelectAllBox" Content="Select All Tools" Margin="0,15,0,0" FontWeight="Bold" Foreground="White" Style="{StaticResource ModernCheckBox}"/>
|
||||
|
||||
<StackPanel Margin="10,5,0,0">
|
||||
<CheckBox Name="AutotaskBox" Content="Autotask" Margin="0,4" Style="{StaticResource ModernCheckBox}"/>
|
||||
<CheckBox Name="DattoBox" Content="Datto RMM" Margin="0,4" Style="{StaticResource ModernCheckBox}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,25,0,0">
|
||||
<Button Name="LoginBtn" Content="Login with Microsoft" Width="180" Height="38" Margin="5" Style="{StaticResource FancyButton}"/>
|
||||
<Button Name="SubmitBtn" Content="Provision" Width="120" Height="38" Margin="5" Style="{StaticResource FancyButton}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Name="StatusBlock" Foreground="LightGreen" TextAlignment="Center" Margin="0,15,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
5
config.ps1
Normal file
5
config.ps1
Normal file
@@ -0,0 +1,5 @@
|
||||
$Global:CLIENT_ID = "8c3edf4c-e5f4-4889-8528-785896bef75d"
|
||||
$Global:TENANT_ID = "common"
|
||||
$Global:AUTHORITY = "https://login.microsoftonline.com/$TENANT_ID"
|
||||
$Global:SCOPES = "User.Read"
|
||||
$Global:WEBHOOK_URL = "https://automate.svstools.ca/webhook/cff4797d-8d38-4005-b4a2-bf2743a5e6e8"
|
||||
185
main.ps1
Normal file
185
main.ps1
Normal file
@@ -0,0 +1,185 @@
|
||||
# main.ps1 (clean, gated execution)
|
||||
|
||||
# Ensure MSAL.PS is installed
|
||||
if (-not (Get-Module -ListAvailable -Name MSAL.PS)) {
|
||||
try {
|
||||
Write-Host "[INFO] Installing MSAL.PS..."
|
||||
Install-Module -Name MSAL.PS -Scope CurrentUser -Force -AllowClobber
|
||||
} catch {
|
||||
[System.Windows.MessageBox]::Show("MSAL.PS install failed: $($_.Exception.Message)", "Error")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
Import-Module MSAL.PS
|
||||
Write-Host "[INFO] MSAL.PS module loaded."
|
||||
|
||||
# Load config and tool modules
|
||||
. "$PSScriptRoot\config.ps1"
|
||||
. "$PSScriptRoot\tools\autotask.ps1"
|
||||
. "$PSScriptRoot\tools\dattormm.ps1"
|
||||
Write-Host "[INFO] Config and tool modules loaded."
|
||||
|
||||
# Load WPF assemblies
|
||||
Add-Type -AssemblyName PresentationFramework
|
||||
Add-Type -AssemblyName PresentationCore
|
||||
Add-Type -AssemblyName WindowsBase
|
||||
Add-Type -AssemblyName System.Xaml
|
||||
|
||||
# Load WPF XAML UI
|
||||
[xml]$xaml = Get-Content "$PSScriptRoot\InstaProvision.xaml"
|
||||
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
|
||||
$window = [Windows.Markup.XamlReader]::Load($reader)
|
||||
if (-not $window) {
|
||||
Write-Host "[ERROR] Failed to load XAML window."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "[INFO] WPF window loaded."
|
||||
|
||||
# Map named UI controls
|
||||
$CompanyNameBox = $window.FindName("CompanyNameBox")
|
||||
$PhoneBox = $window.FindName("PhoneBox")
|
||||
$SelectAllBox = $window.FindName("SelectAllBox")
|
||||
$AutotaskBox = $window.FindName("AutotaskBox")
|
||||
$DattoBox = $window.FindName("DattoBox")
|
||||
$LoginBtn = $window.FindName("LoginBtn")
|
||||
$SubmitBtn = $window.FindName("SubmitBtn")
|
||||
$StatusBlock = $window.FindName("StatusBlock")
|
||||
$phoneLabel = $window.FindName("PhoneLabel")
|
||||
|
||||
$PhoneBox.Visibility = 'Collapsed'
|
||||
$phoneLabel.Visibility = 'Collapsed'
|
||||
$SubmitBtn.Visibility = 'Collapsed'
|
||||
|
||||
$AutotaskBox.Add_Checked({
|
||||
$PhoneBox.Visibility = 'Visible'
|
||||
$phoneLabel.Visibility = 'Visible'
|
||||
})
|
||||
$AutotaskBox.Add_Unchecked({
|
||||
$PhoneBox.Visibility = 'Collapsed'
|
||||
$phoneLabel.Visibility = 'Collapsed'
|
||||
})
|
||||
|
||||
Write-Host "[INFO] UI control references assigned."
|
||||
|
||||
# Global tool credentials
|
||||
$script:toolCredentials = $null
|
||||
|
||||
# Select All logic
|
||||
$SelectAllBox.Add_Checked({
|
||||
$AutotaskBox.IsChecked = $true
|
||||
$DattoBox.IsChecked = $true
|
||||
Write-Host "[UI] Select All: Checked"
|
||||
})
|
||||
$SelectAllBox.Add_Unchecked({
|
||||
$AutotaskBox.IsChecked = $false
|
||||
$DattoBox.IsChecked = $false
|
||||
Write-Host "[UI] Select All: Unchecked"
|
||||
})
|
||||
|
||||
# Login logic
|
||||
$LoginBtn.Add_Click({
|
||||
Write-Host "[LOGIN] Initiating login..."
|
||||
try {
|
||||
$auth = Get-MSALToken -ClientId $CLIENT_ID -TenantId $TENANT_ID -Scopes $SCOPES -Interactive
|
||||
Write-Host "[LOGIN] Token acquired."
|
||||
|
||||
if (-not $auth.AccessToken) {
|
||||
Write-Host "[ERROR] No access token returned."
|
||||
[System.Windows.MessageBox]::Show("Login failed.", "Auth")
|
||||
return
|
||||
}
|
||||
|
||||
$tenantId = $auth.TenantId
|
||||
Write-Host "[DEBUG] Tenant ID: $tenantId"
|
||||
if (-not $tenantId) {
|
||||
[System.Windows.MessageBox]::Show("Tenant ID missing from login result.", "Auth Error")
|
||||
return
|
||||
}
|
||||
|
||||
$headers = @{ "Content-Type" = "application/json" }
|
||||
$body = @{ tenant_id = $tenantId } | ConvertTo-Json -Depth 2
|
||||
Write-Host "[DEBUG] Webhook payload: $body"
|
||||
|
||||
$response = Invoke-RestMethod -Uri $WEBHOOK_URL -Method POST -Headers $headers -Body $body
|
||||
|
||||
if (-not $response) {
|
||||
Write-Host "[ERROR] Webhook returned no data."
|
||||
[System.Windows.MessageBox]::Show("Unauthorized tenant or empty webhook response.", "Auth")
|
||||
return
|
||||
}
|
||||
|
||||
if ($response -is [pscustomobject]) {
|
||||
$script:toolCredentials = @{}
|
||||
foreach ($property in $response.PSObject.Properties) {
|
||||
$script:toolCredentials[$property.Name] = $property.Value
|
||||
}
|
||||
} else {
|
||||
Write-Host "[ERROR] Webhook returned non-object data: $response"
|
||||
[System.Windows.MessageBox]::Show("Invalid webhook response.", "Auth")
|
||||
return
|
||||
}
|
||||
|
||||
$SubmitBtn.Visibility = 'Visible'
|
||||
$LoginBtn.Visibility = 'Collapsed'
|
||||
Write-Host "[INFO] Login and webhook successful."
|
||||
[System.Windows.MessageBox]::Show("Login successful.", "Auth")
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Login/Webhook exception: $($_.Exception.Message)"
|
||||
[System.Windows.MessageBox]::Show("Login/Webhook error: $($_.Exception.Message)", "Error")
|
||||
}
|
||||
})
|
||||
|
||||
# Submit logic
|
||||
$SubmitBtn.Add_Click({
|
||||
$company = $CompanyNameBox.Text.Trim()
|
||||
$phone = $PhoneBox.Text.Trim()
|
||||
$StatusBlock.Text = ""
|
||||
|
||||
Write-Host "[SUBMIT] Provisioning start"
|
||||
Write-Host "[DEBUG] Company: $company"
|
||||
Write-Host "[DEBUG] Phone: $phone"
|
||||
Write-Host "[DEBUG] Autotask selected: $($AutotaskBox.IsChecked)"
|
||||
Write-Host "[DEBUG] Datto selected: $($DattoBox.IsChecked)"
|
||||
Write-Host "[DEBUG] Tool Credentials: $($script:toolCredentials | ConvertTo-Json -Depth 4)"
|
||||
|
||||
if (-not $company) {
|
||||
[System.Windows.MessageBox]::Show("Please enter a company name.", "Missing Info")
|
||||
return
|
||||
}
|
||||
|
||||
if ($AutotaskBox.IsChecked -and ($phone -notmatch "^[\d\s\-\+\(\)]{10,}$")) {
|
||||
[System.Windows.MessageBox]::Show("Valid phone number required for Autotask.", "Invalid Input")
|
||||
return
|
||||
}
|
||||
|
||||
$StatusBlock.Text = "Provisioning in progress..."
|
||||
$window.Dispatcher.Invoke("Background", [action]{ $window.UpdateLayout() })
|
||||
|
||||
try {
|
||||
if ($AutotaskBox.IsChecked) {
|
||||
Write-Host "[INFO] Provisioning Autotask..."
|
||||
Invoke-AutotaskProvision -CompanyName $company -PhoneNumber $phone -Credentials $script:toolCredentials
|
||||
}
|
||||
|
||||
if ($DattoBox.IsChecked) {
|
||||
Write-Host "[INFO] Provisioning Datto RMM..."
|
||||
Invoke-DattoProvision -CompanyName $company -Credentials $script:toolCredentials
|
||||
}
|
||||
|
||||
$StatusBlock.Text = "Provisioning completed successfully."
|
||||
Write-Host "[SUCCESS] Provisioning complete."
|
||||
$CompanyNameBox.Text = ""
|
||||
$PhoneBox.Text = ""
|
||||
$AutotaskBox.IsChecked = $false
|
||||
$DattoBox.IsChecked = $false
|
||||
$SelectAllBox.IsChecked = $false
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Provisioning failed: $($_.Exception.Message)"
|
||||
$StatusBlock.Text = "Provisioning failed: $($_.Exception.Message)"
|
||||
}
|
||||
})
|
||||
|
||||
# Show the window
|
||||
$window.ShowDialog() | Out-Null
|
||||
41
tools/autotask.ps1
Normal file
41
tools/autotask.ps1
Normal file
@@ -0,0 +1,41 @@
|
||||
function Invoke-AutotaskProvision {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$CompanyName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$PhoneNumber,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[hashtable]$Credentials
|
||||
)
|
||||
|
||||
if (-not $Credentials["AutotaskURL"] -or -not $Credentials["AutotaskIntCode"] -or -not $Credentials["AutotaskUsername"] -or -not $Credentials["AutotaskSecret"]) {
|
||||
throw "Missing Autotask credentials in hashtable."
|
||||
}
|
||||
|
||||
$BaseURL = $Credentials["AutotaskURL"]
|
||||
$Url = "https://$BaseURL/atservicesrest/v1.0/companies"
|
||||
|
||||
$Headers = @{
|
||||
"ApiIntegrationcode" = $Credentials["AutotaskIntCode"]
|
||||
"UserName" = $Credentials["AutotaskUsername"]
|
||||
"Secret" = $Credentials["AutotaskSecret"]
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
$Body = @{
|
||||
companyName = $CompanyName
|
||||
companyType = 1
|
||||
phone = $PhoneNumber
|
||||
ownerResourceID = 4
|
||||
} | ConvertTo-Json -Depth 3
|
||||
|
||||
try {
|
||||
$Response = Invoke-RestMethod -Uri $Url -Method Post -Headers $Headers -Body $Body
|
||||
$CompanyID = $Response.itemId, $Response.id, $Response.companyID, $Response.item.id | Where-Object { $_ } | Select-Object -First 1
|
||||
[System.Windows.MessageBox]::Show("Autotask company created with ID: $CompanyID", "Autotask")
|
||||
} catch {
|
||||
throw "[Autotask] Provisioning failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
55
tools/dattormm.ps1
Normal file
55
tools/dattormm.ps1
Normal file
@@ -0,0 +1,55 @@
|
||||
function Invoke-DattoProvision {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$CompanyName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[hashtable]$Credentials
|
||||
)
|
||||
|
||||
if (-not $Credentials["DattoURL"] -or -not $Credentials["DattoKey"] -or -not $Credentials["DattoSecret"]) {
|
||||
throw "Missing Datto RMM credentials in hashtable."
|
||||
}
|
||||
|
||||
$TokenUrl = "$($Credentials["DattoURL"].TrimEnd('/'))/auth/oauth/token"
|
||||
$SiteUrl = "$($Credentials["DattoURL"].TrimEnd('/'))/api/v2/site"
|
||||
|
||||
$TokenBody = @{
|
||||
grant_type = "password"
|
||||
username = $Credentials["DattoKey"]
|
||||
password = $Credentials["DattoSecret"]
|
||||
}
|
||||
|
||||
$TokenHeaders = @{
|
||||
"Content-Type" = "application/x-www-form-urlencoded"
|
||||
"Accept" = "application/json"
|
||||
}
|
||||
|
||||
try {
|
||||
# Construct Basic Auth manually
|
||||
$pair = "public-client:public"
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($pair)
|
||||
$base64 = [System.Convert]::ToBase64String($bytes)
|
||||
$TokenHeaders["Authorization"] = "Basic $base64"
|
||||
|
||||
$TokenResponse = Invoke-RestMethod -Uri $TokenUrl -Method Post -Headers $TokenHeaders -Body $TokenBody
|
||||
$AccessToken = $TokenResponse.access_token
|
||||
} catch {
|
||||
throw "[Datto] Token request failed: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$SiteHeaders = @{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
"Content-Type" = "application/json"
|
||||
"Accept" = "application/json"
|
||||
}
|
||||
|
||||
$SiteBody = @{ name = $CompanyName } | ConvertTo-Json -Depth 3
|
||||
|
||||
try {
|
||||
$SiteResponse = Invoke-RestMethod -Uri $SiteUrl -Method Put -Headers $SiteHeaders -Body $SiteBody
|
||||
[System.Windows.MessageBox]::Show("Datto RMM site created: $CompanyName", "Datto RMM")
|
||||
} catch {
|
||||
throw "[Datto] Site creation failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user