Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [ main, master ]
tags:
- 'v*'
pull_request:
branches: [ main, master ]
workflow_dispatch: # Allows manual triggering

jobs:
Expand Down Expand Up @@ -117,7 +119,10 @@ jobs:

- name: Build
run: dotnet build --configuration Release --no-restore


- name: Run tests
run: dotnet test --configuration Release --no-build --verbosity normal

- name: Publish
run: dotnet publish --configuration Release --runtime ${{ matrix.runtime }} --self-contained false --output ./publish/${{ matrix.runtime }} /p:OutputType=${{ matrix.output-type }}

Expand Down Expand Up @@ -151,28 +156,24 @@ jobs:

- name: Download Windows artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: teams-phonemanager-win-x64
path: ./artifacts/win-x64

- name: Download macOS Intel artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: teams-phonemanager-osx-x64
path: ./artifacts/osx-x64

- name: Download macOS Apple Silicon artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: teams-phonemanager-osx-arm64
path: ./artifacts/osx-arm64

- name: Download Linux artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: teams-phonemanager-linux-x64
path: ./artifacts/linux-x64
Expand Down Expand Up @@ -251,7 +252,7 @@ jobs:
ls -lh artifacts/*.zip 2>/dev/null || echo "No zip files found"

- name: Create Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 # v2.0.8
with:
tag_name: ${{ steps.tag.outputs.tag_name }}
name: ${{ steps.tag.outputs.tag_name }}
Expand Down
20 changes: 10 additions & 10 deletions MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,70 +74,70 @@

<ListBox x:Name="NavigationListBox" Grid.Column="0" Classes="nav"
SelectionChanged="NavigationListBox_SelectionChanged">
<ListBoxItem Tag="Welcome" Classes="nav">
<ListBoxItem Tag="Welcome" Classes="nav" AutomationProperties.Name="Welcome">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Home"/>
<TextBlock Text="Welcome" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="GetStarted" Classes="nav">
<ListBoxItem Tag="GetStarted" Classes="nav" AutomationProperties.Name="Get Started">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Rocket"/>
<TextBlock Text="Get Started" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="Variables" Classes="nav">
<ListBoxItem Tag="Variables" Classes="nav" AutomationProperties.Name="Variables">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Code"/>
<TextBlock Text="Variables" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="M365Groups" Classes="nav">
<ListBoxItem Tag="M365Groups" Classes="nav" AutomationProperties.Name="M365 Groups">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="PeopleTeam"/>
<TextBlock Text="M365 Groups" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="CallQueues" Classes="nav">
<ListBoxItem Tag="CallQueues" Classes="nav" AutomationProperties.Name="Call Queues">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="CallInbound"/>
<TextBlock Text="Call Queues" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="AutoAttendants" Classes="nav">
<ListBoxItem Tag="AutoAttendants" Classes="nav" AutomationProperties.Name="Auto Attendants">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Bot"/>
<TextBlock Text="Auto Attendants" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="Holidays" Classes="nav">
<ListBoxItem Tag="Holidays" Classes="nav" AutomationProperties.Name="Holidays">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Calendar"/>
<TextBlock Text="Holidays" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="Documentation" Classes="nav">
<ListBoxItem Tag="Documentation" Classes="nav" AutomationProperties.Name="Documentation">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="DocumentText"/>
<TextBlock Text="Documentation" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="Wizard" Classes="nav">
<ListBoxItem Tag="Wizard" Classes="nav" AutomationProperties.Name="Setup Wizard">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="Wand"/>
<TextBlock Text="Setup Wizard" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</ListBoxItem>

<ListBoxItem Tag="BulkOperations" Classes="nav">
<ListBoxItem Tag="BulkOperations" Classes="nav" AutomationProperties.Name="Bulk Operations">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="12">
<fi:SymbolIcon Symbol="TableMultiple"/>
<TextBlock Text="Bulk Operations" FontSize="14" VerticalAlignment="Center"/>
Expand Down
6 changes: 3 additions & 3 deletions Services/ConstantsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ public static class PowerShell

public static class Application
{
public const string Version = "Version 3.11.0";
public const string Version = "Version 3.11.1";
public const string Copyright = "Realgar © 2026. MIT License.";
}

public static class Messages
{
public const string WaitingMessage = "Bitte warte bis die vorherige Operation im Backend von Microsoft ankommmt";
public const string LicenseWaitingMessage = "Bitte warte bis die Teams Phone Resource Lizenz richtig gesetzt ist";
public const string WaitingMessage = "Please wait while the previous operation is processed by Microsoft";
public const string LicenseWaitingMessage = "Please wait while the Teams Phone Resource License is being applied";
}

public static class CallQueue
Expand Down
6 changes: 0 additions & 6 deletions Services/ErrorHandlingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ public ErrorHandlingService(ILoggingService loggingService, IDialogService dialo
_dialogService = dialogService;
}

public async Task ShowContentDialogAsync(string title, string message, FluentAvalonia.UI.Controls.ContentDialogButton defaultButton = FluentAvalonia.UI.Controls.ContentDialogButton.Primary)
{
// Delegate to dialog service - defaultButton parameter kept for backward compatibility but ignored
await _dialogService.ShowMessageAsync(title, message);
}

public async Task HandlePowerShellError(string command, string error, string context = "")
{
var cleanCommand = command?.Replace("\r", "").Replace("\n", " ") ?? "";
Expand Down
1 change: 0 additions & 1 deletion Services/Interfaces/IErrorHandlingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ public interface IErrorHandlingService
Task<bool> HandleConfirmation(string message, string title = "Confirmation");
Task ShowSuccess(string message, string title = "Success");
Task ShowInfo(string message, string title = "Information");
Task ShowContentDialogAsync(string title, string message, FluentAvalonia.UI.Controls.ContentDialogButton defaultButton = FluentAvalonia.UI.Controls.ContentDialogButton.Primary);
}
4 changes: 2 additions & 2 deletions Services/LoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ private static string SanitizeLogMessage(string message)
public enum LogLevel
{
Info,
Success,
Warning,
Error,
Success
Error
}
}
6 changes: 3 additions & 3 deletions Services/PowerShellContextService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ public async Task<string> ExecuteCommandAsync(string command, Dictionary<string,
}
}

[Obsolete("Use IsConnectedAsync instead. This synchronous wrapper can deadlock when called from the UI thread.")]
public bool IsConnected(string service)
{
// Synchronous wrapper for backward compatibility
// Deprecated: Use IsConnectedAsync instead
return IsConnectedAsync(service).GetAwaiter().GetResult();
// Return false to avoid deadlock — callers should use IsConnectedAsync
return false;
}

public async Task<bool> IsConnectedAsync(string service, CancellationToken cancellationToken = default)
Expand Down
3 changes: 2 additions & 1 deletion Services/PowerShellSanitizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public string SanitizeString(string input)
.Replace("|", "") // Pipeline
.Replace("&", "") // Command separator
.Replace("<", "") // Input redirection
.Replace(">", ""); // Output redirection
.Replace(">", "") // Output redirection
.Replace("\"", ""); // Double quote (prevents string breakout)

return sanitized;
}
Expand Down
31 changes: 22 additions & 9 deletions Services/ScriptBuilders/BulkOperationsScriptBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using teams_phonemanager.Models;
using teams_phonemanager.Services.Interfaces;
using System;
using System.Collections.Generic;
using System.Globalization;
Expand All @@ -18,17 +19,20 @@ public class BulkOperationsScriptBuilder
private readonly CallQueueScriptBuilder _callQueueBuilder;
private readonly AutoAttendantScriptBuilder _autoAttendantBuilder;
private readonly ResourceAccountScriptBuilder _resourceAccountBuilder;
private readonly IPowerShellSanitizationService _sanitizer;

public BulkOperationsScriptBuilder(
CommonScriptBuilder commonBuilder,
CallQueueScriptBuilder callQueueBuilder,
AutoAttendantScriptBuilder autoAttendantBuilder,
ResourceAccountScriptBuilder resourceAccountBuilder)
ResourceAccountScriptBuilder resourceAccountBuilder,
IPowerShellSanitizationService sanitizer)
{
_commonBuilder = commonBuilder;
_callQueueBuilder = callQueueBuilder;
_autoAttendantBuilder = autoAttendantBuilder;
_resourceAccountBuilder = resourceAccountBuilder;
_sanitizer = sanitizer;
}

/// <summary>
Expand Down Expand Up @@ -134,37 +138,46 @@ public string GenerateBulkScript(List<PhoneManagerVariables> entries)
var vars = entries[i];
var num = i + 1;

// Sanitize all CSV-sourced values for safe use in Write-Host strings
var safeCustomer = _sanitizer.SanitizeString(vars.Customer);
var safeGroupName = _sanitizer.SanitizeString(vars.CustomerGroupName);
var safeM365Group = _sanitizer.SanitizeString(vars.M365Group);
var safeRacqUPN = _sanitizer.SanitizeString(vars.RacqUPN);
var safeCqDisplayName = _sanitizer.SanitizeString(vars.CqDisplayName);
var safeRaaaUPN = _sanitizer.SanitizeString(vars.RaaaUPN);
var safeAaDisplayName = _sanitizer.SanitizeString(vars.AaDisplayName);

sb.AppendLine($"# ──────────────────────────────────────────────────────────────");
sb.AppendLine($"# Entry {num}/{entries.Count}: {vars.Customer} - {vars.CustomerGroupName}");
sb.AppendLine($"# Entry {num}/{entries.Count}: {safeCustomer} - {safeGroupName}");
sb.AppendLine($"# ──────────────────────────────────────────────────────────────");
sb.AppendLine();

sb.AppendLine($"Write-Host '▶ [{num}/{entries.Count}] Processing: {vars.Customer} - {vars.CustomerGroupName}'");
sb.AppendLine($"Write-Host '▶ [{num}/{entries.Count}] Processing: {safeCustomer} - {safeGroupName}'");
sb.AppendLine();

// Step 1: M365 Group
sb.AppendLine($"# Step 1: Create M365 Group");
sb.AppendLine($"Write-Host ' [1/6] Creating M365 Group: {vars.M365Group}'");
sb.AppendLine($"Write-Host ' [1/6] Creating M365 Group: {safeM365Group}'");
sb.AppendLine(_commonBuilder.GetCreateM365GroupCommand(vars.M365Group));
sb.AppendLine();

// Step 2: CQ Resource Account + License
sb.AppendLine($"# Step 2: Create CQ Resource Account + License");
sb.AppendLine($"Write-Host ' [2/6] Creating CQ Resource Account: {vars.RacqUPN}'");
sb.AppendLine($"Write-Host ' [2/6] Creating CQ Resource Account: {safeRacqUPN}'");
sb.AppendLine(_resourceAccountBuilder.GetCreateResourceAccountCommand(vars));
sb.AppendLine(_resourceAccountBuilder.GetUpdateResourceAccountUsageLocationCommand(vars.RacqUPN, vars.UsageLocation));
sb.AppendLine(_commonBuilder.GetAssignLicenseCommand(vars.RacqUPN, vars.SkuId));
sb.AppendLine();

// Step 3: Call Queue
sb.AppendLine($"# Step 3: Create Call Queue");
sb.AppendLine($"Write-Host ' [3/6] Creating Call Queue: {vars.CqDisplayName}'");
sb.AppendLine($"Write-Host ' [3/6] Creating Call Queue: {safeCqDisplayName}'");
sb.AppendLine(_callQueueBuilder.GetCreateCallQueueCommand(vars));
sb.AppendLine();

// Step 4: AA Resource Account + License + Phone
sb.AppendLine($"# Step 4: Create AA Resource Account + License + Phone");
sb.AppendLine($"Write-Host ' [4/6] Creating AA Resource Account: {vars.RaaaUPN}'");
sb.AppendLine($"Write-Host ' [4/6] Creating AA Resource Account: {safeRaaaUPN}'");
sb.AppendLine(_resourceAccountBuilder.GetCreateAutoAttendantResourceAccountCommand(vars));
sb.AppendLine(_resourceAccountBuilder.GetUpdateAutoAttendantResourceAccountUsageLocationCommand(vars.RaaaUPN, vars.UsageLocation));
sb.AppendLine(_resourceAccountBuilder.GetAssignAutoAttendantLicenseCommand(vars.RaaaUPN, vars.SkuId));
Expand All @@ -176,7 +189,7 @@ public string GenerateBulkScript(List<PhoneManagerVariables> entries)

// Step 5: Auto Attendant (monolithic — runs as one connected script)
sb.AppendLine($"# Step 5: Create Auto Attendant");
sb.AppendLine($"Write-Host ' [5/6] Creating Auto Attendant: {vars.AaDisplayName}'");
sb.AppendLine($"Write-Host ' [5/6] Creating Auto Attendant: {safeAaDisplayName}'");
sb.AppendLine(_autoAttendantBuilder.GetCreateAutoAttendantCommand(vars));
sb.AppendLine();

Expand All @@ -186,7 +199,7 @@ public string GenerateBulkScript(List<PhoneManagerVariables> entries)
sb.AppendLine(_autoAttendantBuilder.GetAssociateResourceAccountWithAutoAttendantCommand(vars.RaaaUPN, vars.AaDisplayName));
sb.AppendLine();

sb.AppendLine($"Write-Host '✅ [{num}/{entries.Count}] Complete: {vars.Customer} - {vars.CustomerGroupName}'");
sb.AppendLine($"Write-Host '✅ [{num}/{entries.Count}] Complete: {safeCustomer} - {safeGroupName}'");
sb.AppendLine();
}

Expand Down
Loading