├── Attribution
├── license-5049984.pdf
├── license-5049984_2.pdf
└── license-5049984_3.pdf
├── WgServerforWindows
├── Images
│ ├── info.png
│ ├── logo.ico
│ ├── logo.png
│ ├── logo.xcf
│ ├── cancel.png
│ ├── checked.png
│ └── logo_small.png
├── lib
│ └── ExplorerSearchBox.dll
├── App.xaml
├── Controls
│ ├── SelectionWindow.xaml.cs
│ ├── UnhandledErrorWindow.xaml.cs
│ ├── ConfigurationEditorControl.xaml.cs
│ ├── ServerStatusWindow.xaml.cs
│ ├── PrerequisiteItemControl.xaml.cs
│ ├── SplashScreen.xaml.cs
│ ├── ServerConfigurationEditorWindow.xaml.cs
│ ├── ServerStatusWindow.xaml
│ ├── SplashScreen.xaml
│ ├── ServerConfigurationEditorWindow.xaml
│ ├── ClientConfigurationEditorWindow.xaml.cs
│ ├── UnhandledErrorWindow.xaml
│ ├── SelectionWindow.xaml
│ └── ClientConfigurationEditorWindow.xaml
├── Models
│ ├── MainWindowModel.cs
│ ├── NatPrerequisiteGroup.cs
│ ├── PrerequisiteGroup.cs
│ ├── EmptyStringValidation.cs
│ ├── TemporaryFile.cs
│ ├── SettingsPrerequisite.cs
│ ├── OpenClientConfigDirectorySubCommand.cs
│ ├── OpenServerConfigDirectorySubCommand.cs
│ ├── TunnelServiceNameSubCommand.cs
│ ├── ClientConfigurationList.cs
│ ├── WireGuardExePrerequisite.cs
│ ├── BootTaskDelaySubCommand.cs
│ ├── UnhandledErrorWindowModel.cs
│ ├── ConfigurationPropertyAction.cs
│ ├── ChangeClientConfigDirectorySubCommand.cs
│ ├── ChangeServerConfigDirectorySubCommand.cs
│ ├── PrivateNetworkTaskSubCommand.cs
│ ├── NewNetIpAddressTaskSubCommand.cs
│ ├── GlobalAppSettings.cs
│ ├── AppSettings.cs
│ ├── SelectionWindowModel.cs
│ ├── NetNatRangeSubCommand.cs
│ ├── ConfigurationProperty.cs
│ ├── PrivateNetworkPrerequisite.cs
│ ├── ServerStatusPrerequisite.cs
│ ├── TunnelServicePrerequisite.cs
│ ├── PersistentInternetSharingPrerequisite.cs
│ └── PrerequisiteItem.cs
├── ShortcutCommands.cs
├── AssemblyInfo.cs
├── MyUpdateChecker.cs
├── Converters
│ ├── MultiValueStringFormatConverter.cs
│ ├── ResourceStringConverter.cs
│ └── LastIndexVisibilityConverter.cs
├── Extensions
│ ├── BitmapExtensions.cs
│ └── ConfigurationExtensions.cs
├── MainWindow.xaml
├── WaitCursor.cs
├── TimeCachedProperty.cs
├── Properties
│ └── app.manifest
└── WgServerforWindows.csproj
├── WireGuardServerForWindows
├── .keep
├── VersionInfo2.xml
└── AppUpdate.xsd
├── WgAPI
├── WgAPI.csproj
├── Commands
│ ├── ShowCommand.cs
│ ├── SyncConfigurationCommand.cs
│ ├── UninstallTunnelServiceCommand.cs
│ ├── InstallTunnelServiceCommand.cs
│ ├── GenerateKeyCommandImplementations.cs
│ ├── WireGuardCommand.cs
│ ├── GenerateKeyCommand.cs
│ └── UninstallCommand.cs
└── WireGuardExe.cs
├── WgServerforWindows.Cli.Options
├── SetPathCommand.cs
├── PrivateNetworkCommand.cs
├── StatusCommand.cs
├── SetNetIpAddressCommand.cs
├── RestartInternetSharingCommand.cs
├── WgServerforWindows.Cli.Options.csproj
├── Utilities.cs
└── Properties
│ ├── Resources.resx
│ └── Resources.Designer.cs
├── Directory.Build.props
├── Installer
├── GenerateRelease.ps1
├── README.md
├── UpdateVersions.ps1
└── WS4WSetupScript.iss
├── WgServerforWindows.Tests
├── WgServerforWindows.Tests.csproj
└── ServerConfigurationEndpointPropertyTests.cs
├── .github
└── workflows
│ ├── restore_build_test.yml
│ ├── update_versions.yml
│ ├── create_release_branch.yml
│ └── generate_release.yml
├── LICENSE
├── WgServerforWindowsCli
├── WgServerforWindows.Cli.csproj
├── Program.cs
└── Properties
│ ├── app.manifest
│ ├── Resources.Designer.cs
│ └── Resources.resx
└── WgServerforWindows.sln
/Attribution/license-5049984.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/Attribution/license-5049984.pdf
--------------------------------------------------------------------------------
/Attribution/license-5049984_2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/Attribution/license-5049984_2.pdf
--------------------------------------------------------------------------------
/Attribution/license-5049984_3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/Attribution/license-5049984_3.pdf
--------------------------------------------------------------------------------
/WgServerforWindows/Images/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/info.png
--------------------------------------------------------------------------------
/WgServerforWindows/Images/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/logo.ico
--------------------------------------------------------------------------------
/WgServerforWindows/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/logo.png
--------------------------------------------------------------------------------
/WgServerforWindows/Images/logo.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/logo.xcf
--------------------------------------------------------------------------------
/WgServerforWindows/Images/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/cancel.png
--------------------------------------------------------------------------------
/WgServerforWindows/Images/checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/checked.png
--------------------------------------------------------------------------------
/WgServerforWindows/Images/logo_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/Images/logo_small.png
--------------------------------------------------------------------------------
/WgServerforWindows/lib/ExplorerSearchBox.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micahmo/WgServerforWindows/HEAD/WgServerforWindows/lib/ExplorerSearchBox.dll
--------------------------------------------------------------------------------
/WireGuardServerForWindows/.keep:
--------------------------------------------------------------------------------
1 | This folder exists for auto-update backward compatibility. It is not intended to imply any association with WireGuard.
--------------------------------------------------------------------------------
/WgAPI/WgAPI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net80-windows
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/WgServerforWindows/App.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WgAPI/Commands/ShowCommand.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI.Commands
2 | {
3 | public class ShowCommand : WireGuardCommand
4 | {
5 | public ShowCommand(string interfaceName) : base
6 | (
7 | @switch: "show",
8 | whichExe: WhichExe.WGExe,
9 | args: interfaceName
10 | )
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/SetPathCommand.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using WgServerforWindows.Cli.Options.Properties;
3 |
4 | namespace WgServerforWindows.Cli.Options
5 | {
6 | [Verb("setpath", HelpText = nameof(Resources.SetPathHelpText), ResourceType = typeof(Resources))]
7 | public class SetPathCommand
8 | {
9 | // No options for this command
10 | }
11 | }
--------------------------------------------------------------------------------
/WgAPI/Commands/SyncConfigurationCommand.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI.Commands
2 | {
3 | public class SyncConfigurationCommand : WireGuardCommand
4 | {
5 | public SyncConfigurationCommand(string interfaceName, string configurationFile) : base("syncconf", WhichExe.WGExe)
6 | {
7 | Args = new[] {interfaceName, configurationFile};
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/PrivateNetworkCommand.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using WgServerforWindows.Cli.Options.Properties;
3 |
4 | namespace WgServerforWindows.Cli.Options
5 | {
6 | [Verb("privatenetwork", HelpText = nameof(Resources.PrivateNetworkHelpText), ResourceType = typeof(Resources))]
7 | public class PrivateNetworkCommand
8 | {
9 | // No options for this command
10 | }
11 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2.1.4.0
5 | 2.1.4.0
6 | 2.1.4.0
7 | Micah Morrison
8 | WS4W
9 |
10 |
11 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/SelectionWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace WgServerforWindows.Controls
4 | {
5 | ///
6 | /// Interaction logic for SelectionWindow.xaml
7 | ///
8 | public partial class SelectionWindow : Window
9 | {
10 | public SelectionWindow()
11 | {
12 | InitializeComponent();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/StatusCommand.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace WgServerforWindows.Cli.Options
4 | {
5 | ///
6 | /// Instructs the application to show the Status window.
7 | /// Only invokable through the main executable, not the CLI.
8 | ///
9 | [Verb("status")]
10 | public class StatusCommand
11 | {
12 | // No options for this command
13 | }
14 | }
--------------------------------------------------------------------------------
/WgAPI/Commands/UninstallTunnelServiceCommand.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI.Commands
2 | {
3 | public class UninstallTunnelServiceCommand : WireGuardCommand
4 | {
5 | public UninstallTunnelServiceCommand(string serviceName) : base
6 | (
7 | @switch: "/uninstalltunnelservice",
8 | whichExe: WhichExe.WireGuardExe,
9 | serviceName
10 | )
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/UnhandledErrorWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace WgServerforWindows.Controls
4 | {
5 | ///
6 | /// Interaction logic for UnhandledErrorWindow.xaml
7 | ///
8 | public partial class UnhandledErrorWindow : Window
9 | {
10 | public UnhandledErrorWindow()
11 | {
12 | InitializeComponent();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WgAPI/Commands/InstallTunnelServiceCommand.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI.Commands
2 | {
3 | public class InstallTunnelServiceCommand : WireGuardCommand
4 | {
5 | public InstallTunnelServiceCommand(string configurationFile) : base
6 | (
7 | @switch: "/installtunnelservice",
8 | whichExe: WhichExe.WireGuardExe,
9 | configurationFile
10 | )
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/MainWindowModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using GalaSoft.MvvmLight;
3 |
4 | namespace WgServerforWindows.Models
5 | {
6 | public class MainWindowModel : ObservableObject
7 | {
8 | public MainWindowModel()
9 | {
10 |
11 | }
12 |
13 | public List PrerequisiteItems { get; set; } = new List();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ConfigurationEditorControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Controls;
2 |
3 | namespace WgServerforWindows.Controls
4 | {
5 | ///
6 | /// Interaction logic for ServerConfigurationEditor.xaml
7 | ///
8 | public partial class ConfigurationEditorControl : Grid
9 | {
10 | public ConfigurationEditorControl()
11 | {
12 | InitializeComponent();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/NatPrerequisiteGroup.cs:
--------------------------------------------------------------------------------
1 | namespace WgServerforWindows.Models
2 | {
3 | public class NatPrerequisiteGroup : PrerequisiteGroup
4 | {
5 | public NatPrerequisiteGroup(NewNetNatPrerequisite newNetNatPrerequisite, InternetSharingPrerequisite internetSharingPrerequisite, PersistentInternetSharingPrerequisite persistentInternetSharingPrerequisite) : base
6 | (
7 | newNetNatPrerequisite, internetSharingPrerequisite, persistentInternetSharingPrerequisite
8 | )
9 | {
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/SetNetIpAddressCommand.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using WgServerforWindows.Cli.Options.Properties;
3 |
4 | namespace WgServerforWindows.Cli.Options
5 | {
6 | [Verb("setnetipaddress", HelpText = nameof(Resources.SetNetIpAddressHelpText), ResourceType = typeof(Resources))]
7 | public class SetNetIpAddressCommand
8 | {
9 | [Option("serverdatapath", HelpText = nameof(Resources.SetNetIpAddressCommandServerDataPathHelpText), ResourceType = typeof(Resources))]
10 | public string ServerDataPath { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/RestartInternetSharingCommand.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using WgServerforWindows.Cli.Options.Properties;
3 |
4 | namespace WgServerforWindows.Cli.Options
5 | {
6 | [Verb("restartinternetsharing", HelpText = nameof(Resources.RestartInternetSharingHelpText), ResourceType = typeof(Resources))]
7 | public class RestartInternetSharingCommand
8 | {
9 | [Option("network", HelpText = nameof(Resources.RestartInternetSharingNetworkHelpText), ResourceType = typeof(Resources))]
10 | public string NetworkToShare { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/PrerequisiteGroup.cs:
--------------------------------------------------------------------------------
1 | namespace WgServerforWindows.Models
2 | {
3 | public abstract class PrerequisiteGroup : PrerequisiteItem
4 | {
5 | protected PrerequisiteGroup(params PrerequisiteItem[] prerequisiteItems) : base
6 | (
7 | string.Empty, string.Empty, string.Empty, string.Empty, string.Empty
8 | )
9 | {
10 | foreach (PrerequisiteItem prerequisiteItem in prerequisiteItems)
11 | {
12 | Children.Add(prerequisiteItem);
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/EmptyStringValidation.cs:
--------------------------------------------------------------------------------
1 | namespace WgServerforWindows.Models
2 | {
3 | public class EmptyStringValidation : ConfigurationPropertyValidation
4 | {
5 | public EmptyStringValidation(string errorMessageIfEmptyString)
6 | {
7 | Validate = obj =>
8 | {
9 | string result = default;
10 |
11 | if (string.IsNullOrEmpty(obj.Value))
12 | {
13 | result = errorMessageIfEmptyString;
14 | }
15 |
16 | return result;
17 | };
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WgAPI/Commands/GenerateKeyCommandImplementations.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI.Commands
2 | {
3 | public class GeneratePrivateKeyCommand : GenerateKeyCommand
4 | {
5 | public GeneratePrivateKeyCommand() : base(KeyType.PrivateKey) { }
6 | }
7 |
8 | public class GeneratePresharedKeyCommand : GenerateKeyCommand
9 | {
10 | public GeneratePresharedKeyCommand() : base(KeyType.PresharedKey) { }
11 | }
12 |
13 | public class GeneratePublicKeyCommand : GenerateKeyCommand
14 | {
15 | public GeneratePublicKeyCommand(string privateKey) : base(KeyType.PublicKey, seedKey: privateKey) { }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/WgServerforWindows/ShortcutCommands.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Input;
2 |
3 | namespace WgServerforWindows
4 | {
5 | public static class ShortcutCommands
6 | {
7 | #region Commands
8 |
9 | static ShortcutCommands()
10 | {
11 | AboutBoxCommand.InputGestures.Add(new KeyGesture(Key.F1));
12 | RefreshCommand.InputGestures.Add(new KeyGesture(Key.F5));
13 | }
14 |
15 | public static RoutedCommand AboutBoxCommand { get; } = new RoutedCommand();
16 |
17 | public static RoutedCommand RefreshCommand { get; } = new RoutedCommand();
18 |
19 | #endregion
20 | }
21 | }
--------------------------------------------------------------------------------
/WgServerforWindows/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/WgServerforWindows/MyUpdateChecker.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using Bluegrams.Application;
4 |
5 | namespace WgServerforWindows
6 | {
7 | public class MyUpdateChecker : WpfUpdateChecker
8 | {
9 | public MyUpdateChecker(string url, Window owner = null, string identifier = null) : base(url, owner, identifier)
10 | {
11 | }
12 |
13 | public override void ShowUpdateDownload(string file)
14 | {
15 | // All we have to do is start the installer.
16 | // It will handle killing this instance of the app and restarting it at the end.
17 | Process.Start(file);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ServerStatusWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using WgServerforWindows.Models;
4 |
5 | namespace WgServerforWindows.Controls
6 | {
7 | ///
8 | /// Interaction logic for ServerStatusWindow.xaml
9 | ///
10 | public partial class ServerStatusWindow : Window
11 | {
12 | public ServerStatusWindow()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | protected override void OnSourceInitialized(EventArgs e)
18 | {
19 | base.OnSourceInitialized(e);
20 |
21 | AppSettings.Instance.Tracker.Track(this);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Installer/GenerateRelease.ps1:
--------------------------------------------------------------------------------
1 | # This script is intended to be run from the root of the repo, like .\Installer\GenerateRelease.ps1
2 | # This script is intended to be run after the "Bump version..." commit has already been pushed.
3 | # Requires that "C:\Program Files (x86)\Inno Setup 6" be in the PATH for iscc.
4 |
5 | foreach ($configuration in "Debug", "Release") {
6 | if (Test-Path -Path WgServerforWindows\bin\$configuration) {
7 | Remove-Item -Recurse WgServerforWindows\bin\$configuration
8 | }
9 | }
10 |
11 | dotnet restore
12 | msbuild WgServerforWindows.sln /property:Configuration=Release
13 |
14 | Remove-Item Installer\WS4WSetup-*.exe
15 |
16 | iscc Installer\WS4WSetupScript.iss
17 |
18 | Start-Process Installer
19 |
--------------------------------------------------------------------------------
/WgAPI/Commands/WireGuardCommand.cs:
--------------------------------------------------------------------------------
1 | namespace WgAPI
2 | {
3 | public class WireGuardCommand
4 | {
5 | public WireGuardCommand(string @switch, WhichExe whichExe, params string[] args)
6 | {
7 | Switch = @switch;
8 | WhichExe = whichExe;
9 | Args = args;
10 | }
11 |
12 | public string Switch { get; protected set; }
13 |
14 | public string[] Args { get; protected set; }
15 |
16 | public WhichExe WhichExe { get; protected set; }
17 |
18 | public string StandardInput { get; protected set; } = string.Empty;
19 | }
20 |
21 | public enum WhichExe
22 | {
23 | WireGuardExe,
24 | WGExe,
25 | Custom,
26 | CustomInteractive
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WireGuardServerForWindows/VersionInfo2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 2.1.4.0
6 | 2025-03-17
7 |
8 | https://github.com/micahmo/WgServerforWindows/releases/download/v2.1.4/WS4WSetup-2.1.4.exe
9 | WS4WSetup-2.1.4.exe
10 |
11 | - Fix issue where NAT boot task could fail (#139)
12 |
13 |
14 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/PrerequisiteItemControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace WgServerforWindows.Controls
5 | {
6 | ///
7 | /// Interaction logic for PrerequisiteItemControl.xaml
8 | ///
9 | public partial class PrerequisiteItemControl : UserControl
10 | {
11 | public static readonly DependencyProperty IsChildProperty = DependencyProperty.Register(nameof(IsChild), typeof(bool), typeof(PrerequisiteItemControl));
12 |
13 | public PrerequisiteItemControl()
14 | {
15 | InitializeComponent();
16 | }
17 |
18 | public bool IsChild
19 | {
20 | get => (bool)GetValue(IsChildProperty);
21 | set => SetValue(IsChildProperty, value);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/WgServerforWindows.Cli.Options.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net80-windows
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | True
14 | True
15 | Resources.resx
16 |
17 |
18 |
19 |
20 |
21 | PublicResXFileCodeGenerator
22 | Resources.Designer.cs
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/WgServerforWindows.Tests/WgServerforWindows.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net80-windows
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/restore_build_test.yml:
--------------------------------------------------------------------------------
1 | name: Run Unit Tests
2 | concurrency: ${{ github.workflow }}
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | restore_build_test:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v4
12 |
13 | # Setup: Need .NET Core for restore, MSBuild for build, and VSTest for test
14 | - name: Setup .NET
15 | uses: actions/setup-dotnet@v4
16 | with:
17 | dotnet-version: 8.0.x
18 | - name: Setup MSBuild
19 | uses: microsoft/setup-msbuild@v2
20 | - name: Setup VSTest
21 | uses: darenm/Setup-VSTest@v1.2
22 |
23 | # Restore, build, test
24 | - name: Restore
25 | run: dotnet restore
26 | - name: Build
27 | run: msbuild
28 | - name: Test
29 | run: vstest.console WgServerforWindows.Tests\bin\Debug\net80-windows\WgServerforWindows.Tests.dll
30 |
--------------------------------------------------------------------------------
/Installer/README.md:
--------------------------------------------------------------------------------
1 | # Installer
2 |
3 | ## Prerequisite
4 |
5 | Download and install [Inno Setup](https://jrsoftware.org/isinfo.php).
6 |
7 | Download the [.NET 8.0 Desktop Runtime (v8.0.11)](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.11-windows-x64-installer) and place it in `WgServerforWindows\Installer`.
8 |
9 | ## Generate Installer for New Version
10 |
11 | Open the main `WgServerforWindows.sln` in Visual Studio.
12 | * Change the build configuration to Release.
13 | * Edit `VersionInfo2.xml` to include the latest version, release date, and download path.
14 | * Bump assembly versions in `Directory.Build.props`.
15 | * Rebuild the solution
16 |
17 | > It's probably a good idea to commit at this point so that the installer is generated from committed code.
18 |
19 | Open `WS4WSetupScript.iss` in Inno Setup.
20 | * Bump the `MyAppVersion` preprocessor definition.
21 | * Compile.
22 |
23 | Create a new release on GitHub and upload the installer.
--------------------------------------------------------------------------------
/WgServerforWindows/Converters/MultiValueStringFormatConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Data;
6 |
7 | namespace WgServerforWindows.Converters
8 | {
9 | public class MultiValueStringFormatConverter : IMultiValueConverter
10 | {
11 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | string result = default;
14 |
15 | if (parameter is string format)
16 | {
17 | result = string.Format(format, values.Select(v => v == DependencyProperty.UnsetValue ? string.Empty : v).ToArray());
18 | }
19 |
20 | return result?.Trim();
21 | }
22 |
23 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
24 | {
25 | throw new NotImplementedException();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WgServerforWindows/Extensions/BitmapExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Runtime.InteropServices;
4 | using System.Windows;
5 | using System.Windows.Interop;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Imaging;
8 |
9 | namespace WgServerforWindows.Extensions
10 | {
11 | public static class BitmapExtensions
12 | {
13 | [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
14 | [return: MarshalAs(UnmanagedType.Bool)]
15 | public static extern bool DeleteObject([In] IntPtr hObject);
16 |
17 | public static ImageSource ToImageSource(this Bitmap bitmap)
18 | {
19 | var handle = bitmap.GetHbitmap();
20 | try
21 | {
22 | return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
23 | }
24 | finally { DeleteObject(handle); }
25 | }
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WgAPI/Commands/GenerateKeyCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WgAPI.Commands
4 | {
5 | public abstract class GenerateKeyCommand : WireGuardCommand
6 | {
7 | protected GenerateKeyCommand(KeyType keyType, string seedKey = "") : base
8 | (
9 | @switch: keyType.ToCommand(),
10 | whichExe: WhichExe.WGExe
11 | )
12 | {
13 | StandardInput = seedKey;
14 | }
15 | }
16 |
17 | public enum KeyType
18 | {
19 | PrivateKey,
20 | PresharedKey,
21 | PublicKey
22 | }
23 |
24 | internal static class KeyTypeExtensions
25 | {
26 | public static string ToCommand(this KeyType keyType) => keyType switch
27 | {
28 | KeyType.PrivateKey => "genkey",
29 | KeyType.PresharedKey => "genpsk",
30 | KeyType.PublicKey => "pubkey",
31 | _ => throw new ArgumentOutOfRangeException(nameof(keyType), keyType, null)
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/SplashScreen.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Windows;
3 | using System.Windows.Threading;
4 |
5 | namespace WgServerforWindows.Controls
6 | {
7 | ///
8 | /// Interaction logic for SplashScreen.xaml
9 | ///
10 | public partial class SplashScreen : Window
11 | {
12 | public SplashScreen()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | private void Window_Loaded(object sender, RoutedEventArgs e)
18 | {
19 | // Use BeginInvoke to put it at the end of the message queue.
20 | // In other words, let the splash screen have full priority in loading first.
21 | Dispatcher.BeginInvoke(() =>
22 | {
23 | WaitCursor.SetOverrideCursor(null);
24 | WaitCursor.IgnoreOverrideCursor = true;
25 | new MainWindow().Show();
26 | });
27 | }
28 |
29 | public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Micah Morrison
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/TemporaryFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace WgServerforWindows.Models
5 | {
6 | ///
7 | /// Allows creating a temporary file that is automatically deleted when disposed
8 | ///
9 | public class TemporaryFile : IDisposable
10 | {
11 | public TemporaryFile(string originalFilePath, string newFilePath)
12 | {
13 | OriginalFilePath = originalFilePath;
14 | NewFilePath = newFilePath;
15 |
16 | if (TemporaryFileIsNeeded)
17 | {
18 | File.Copy(OriginalFilePath, NewFilePath, overwrite: true);
19 | }
20 | }
21 |
22 | public string OriginalFilePath { get; init; }
23 |
24 | public string NewFilePath { get; init; }
25 |
26 | public bool TemporaryFileIsNeeded => NewFilePath != OriginalFilePath;
27 |
28 | ///
29 | public void Dispose()
30 | {
31 | if (TemporaryFileIsNeeded && File.Exists(NewFilePath))
32 | {
33 | File.Delete(NewFilePath);
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/WgServerforWindows/Converters/ResourceStringConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace WgServerforWindows.Converters
6 | {
7 | public class ResourceStringConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | string result = default;
12 |
13 | try
14 | {
15 | result = Properties.Resources.ResourceManager.GetString(value?.ToString());
16 | }
17 | catch
18 | {
19 | // Ignore resource exceptions
20 | }
21 | finally
22 | {
23 | if (string.IsNullOrEmpty(result))
24 | {
25 | result = $"***{value}***";
26 | }
27 | }
28 |
29 | return result;
30 | }
31 |
32 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
33 | {
34 | // Should never try to convert back.
35 | throw new NotImplementedException();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ServerConfigurationEditorWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using WgServerforWindows.Models;
4 |
5 | namespace WgServerforWindows.Controls
6 | {
7 | ///
8 | /// Interaction logic for ServerConfigurationEditor.xaml
9 | ///
10 | public partial class ServerConfigurationEditorWindow : Window
11 | {
12 | public ServerConfigurationEditorWindow()
13 | {
14 | InitializeComponent();
15 | }
16 |
17 | protected override void OnActivated(EventArgs e)
18 | {
19 | WaitCursor.SetOverrideCursor(null);
20 | }
21 |
22 | protected override void OnSourceInitialized(EventArgs e)
23 | {
24 | base.OnSourceInitialized(e);
25 |
26 | AppSettings.Instance.Tracker.Track(this);
27 | }
28 |
29 | #region Event handlers
30 |
31 | private void SaveButton_Click(object sender, RoutedEventArgs e)
32 | {
33 | DialogResult = true;
34 | Close();
35 | }
36 |
37 | private void CancelButton_Click(object sender, RoutedEventArgs e)
38 | {
39 | DialogResult = false;
40 | Close();
41 | }
42 |
43 | #endregion
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.github/workflows/update_versions.yml:
--------------------------------------------------------------------------------
1 | name: 2. Update Versions
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version_notes:
7 | description: 'Version Notes (example, including quotes: " - First change`n - Second Change")'
8 |
9 | jobs:
10 | update_versions:
11 | runs-on: windows-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 | - name: Determine Version Number
17 | id: determine_version_number
18 | run: echo "VERSION_NUMBER=$($env:GITHUB_REF_NAME.Replace('release/v', ''))" >> $env:GITHUB_OUTPUT
19 | - name: Run UpdateVersions.ps1
20 | run: Installer\UpdateVersions.ps1 ${{ steps.determine_version_number.outputs.VERSION_NUMBER }} ${{ github.event.inputs.version_notes }}
21 | - name: Create Pull Request
22 | uses: peter-evans/create-pull-request@main
23 | with:
24 | commit-message: Bump version to ${{ steps.determine_version_number.outputs.VERSION_NUMBER }}
25 | title: Bump version to ${{ steps.determine_version_number.outputs.VERSION_NUMBER }}
26 | body: >
27 | Bump version to ${{ steps.determine_version_number.outputs.VERSION_NUMBER }}
28 | branch: feature/update-versions-${{ steps.determine_version_number.outputs.VERSION_NUMBER }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/create_release_branch.yml:
--------------------------------------------------------------------------------
1 | name: 1. Create Release Branch
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | increment:
7 | type: choice
8 | description: 'SemVer segment to increment'
9 | required: true
10 | default: 'bug'
11 | options:
12 | - major
13 | - feature
14 | - bug
15 | - alpha
16 | - beta
17 | - pre
18 | - rc
19 |
20 | jobs:
21 | create_release_branch:
22 | runs-on: ubuntu-latest
23 | steps:
24 | - name: Determine Latest Release
25 | id: latest
26 | uses: pozetroninc/github-action-get-latest-release@master
27 | with:
28 | repository: ${{ github.repository }}
29 | excludes: prerelease, draft
30 | - name: Increment Version Number
31 | id: increment_version
32 | uses: christian-draeger/increment-semantic-version@master
33 | with:
34 | current-version: ${{ steps.latest.outputs.release }}
35 | version-fragment: ${{ github.event.inputs.increment }}
36 | - name: Create Release Branch
37 | uses: peterjgrainger/action-create-branch@v2.2.0
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 | with:
41 | branch: refs/heads/release/v${{ steps.increment_version.outputs.next-version }}
42 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/Utilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using CommandLine;
4 |
5 | namespace WgServerforWindows.Cli.Options
6 | {
7 | ///
8 | /// Static utilities related to the CLI options
9 | ///
10 | public static class Utilities
11 | {
12 | ///
13 | /// If the given has the , returns the value.
14 | /// Otherwise, returns null.
15 | ///
16 | ///
17 | public static string GetVerb(this Type verbType)
18 | {
19 | return verbType.GetCustomAttributes(inherit: true).OfType().FirstOrDefault()?.Name;
20 | }
21 |
22 | ///
23 | /// If the given has a property with the , returns the value.
24 | /// Otherwise, returns null.
25 | ///
26 | public static string GetOption(this Type verbType, string optionName)
27 | {
28 | return verbType.GetProperties().FirstOrDefault(p => p.Name == optionName)?.GetCustomAttributes(inherit: true).OfType().FirstOrDefault()?.LongName;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/WgAPI/Commands/UninstallCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Runtime.Versioning;
3 | using Microsoft.Win32;
4 |
5 | namespace WgAPI
6 | {
7 | [SupportedOSPlatform("windows")] // Suppress warnings about registry not working on all platforms
8 | public class UninstallCommand : WireGuardCommand
9 | {
10 | public UninstallCommand() : base(string.Empty, WhichExe.Custom)
11 | {
12 | Args = FindUninstallCommand().Split();
13 | }
14 |
15 | // Inspired by https://stackoverflow.com/a/7206715/4206279
16 | private string FindUninstallCommand()
17 | {
18 | string result = default;
19 |
20 | RegistryKey uninstallKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
21 | foreach (string installedApplication in uninstallKey?.GetSubKeyNames() ?? Enumerable.Empty())
22 | {
23 | RegistryKey installedApplicationKey = Registry.LocalMachine.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{installedApplication}");
24 | if (installedApplicationKey?.GetValue("DisplayName")?.ToString() == "WireGuard")
25 | {
26 | result = installedApplicationKey.GetValue("UninstallString").ToString();
27 | }
28 | }
29 |
30 | return result;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/WgServerforWindows/Extensions/ConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using SharpConfig;
2 |
3 | namespace WgServerforWindows.Extensions
4 | {
5 | public static class ConfigurationExtensions
6 | {
7 | ///
8 | /// Merges two s together. Handles the case where one or both is null.
9 | ///
10 | public static Configuration Merge(this Configuration first, Configuration second)
11 | {
12 | Configuration result = default;
13 |
14 | if (first is { } && second is { })
15 | {
16 | foreach (Section section in second)
17 | {
18 | // Can't use indexer to add new sections in case there are duplicate names during the merge.
19 | Section newSection = new Section(section.Name) {PreComment = section.PreComment};
20 |
21 | foreach (Setting setting in section)
22 | {
23 | newSection[setting.Name].RawValue = setting.RawValue;
24 | }
25 |
26 | first.Add(newSection);
27 | }
28 |
29 | result = first;
30 | }
31 | else if (first is { })
32 | {
33 | result = first;
34 | }
35 | else if (second is { })
36 | {
37 | result = second;
38 | }
39 |
40 | return result;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/WgServerforWindowsCli/WgServerforWindows.Cli.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net80-windows
6 | ws4w
7 | Wg Server for Windows CLI
8 |
9 |
10 |
11 | ..\WgServerforWindows\bin\Debug\
12 |
13 |
14 |
15 | ..\WgServerforWindows\bin\Release\
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | True
30 | True
31 | Resources.resx
32 |
33 |
34 |
35 |
36 |
37 | PublicResXFileCodeGenerator
38 | Resources.Designer.cs
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ServerStatusWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/WgServerforWindows/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/SettingsPrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using WgServerforWindows.Controls;
3 | using WgServerforWindows.Properties;
4 |
5 | namespace WgServerforWindows.Models
6 | {
7 | public class SettingsPrerequisite : PrerequisiteItem
8 | {
9 | #region PrerequisiteItem members
10 |
11 | public SettingsPrerequisite(BootTaskDelaySubCommand bootTaskDelaySubCommand) : base
12 | (
13 | title: string.Empty,
14 | successMessage: string.Empty,
15 | errorMessage: string.Empty,
16 | resolveText: string.Empty,
17 | configureText: Resources.SettingsConfigure
18 | )
19 | {
20 | SubCommands.Add(bootTaskDelaySubCommand);
21 | }
22 |
23 | public override BooleanTimeCachedProperty Fulfilled { get; } = new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () => true);
24 |
25 | public override BooleanTimeCachedProperty HasIcon { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => false);
26 |
27 | public override void Resolve()
28 | {
29 | throw new NotImplementedException();
30 | }
31 |
32 | public override void Configure()
33 | {
34 | if (Control is PrerequisiteItemControl prerequisiteItemControl)
35 | {
36 | prerequisiteItemControl.SplitButtonFulfilled.IsOpen = true;
37 | }
38 | }
39 |
40 | public override BooleanTimeCachedProperty IsInformational { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => true);
41 |
42 | #endregion
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/WgServerforWindowsCli/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using CliWrap;
4 | using CliWrap.Buffered;
5 | using CommandLine;
6 | using CommandLine.Text;
7 | using WgServerforWindows.Cli.Options;
8 | using WgServerforWindows.Cli.Properties;
9 |
10 | namespace WgServerforWindows.Cli
11 | {
12 | class Program
13 | {
14 | static void Main(string[] args)
15 | {
16 | var parser = Parser.Default.ParseArguments(args);
17 |
18 | parser.WithParsed(o =>
19 | {
20 | // If it parses successfully, then just pass along to main exe.
21 | try
22 | {
23 | var result = CliWrap.Cli.Wrap("WgServerforWindows.exe")
24 | .WithArguments(args)
25 | .WithValidation(CommandResultValidation.None)
26 | .ExecuteBufferedAsync().GetAwaiter().GetResult();
27 |
28 | Console.Write(result.StandardOutput);
29 | Console.WriteLine(result.ExitCode == 0 ? Resources.CommandSucceeded : Resources.CommandFailed);
30 |
31 | Environment.Exit(result.ExitCode);
32 | }
33 | catch (Exception ex) when (ex is Win32Exception || ex.InnerException is Win32Exception)
34 | {
35 | Console.WriteLine(Resources.MustRunAsAdmin);
36 | Environment.Exit(1);
37 | }
38 | });
39 |
40 | parser.WithNotParsed(_ => HelpText.AutoBuild(parser));
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/generate_release.yml:
--------------------------------------------------------------------------------
1 | name: 3. Generate Release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | generate_release:
8 | runs-on: windows-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - name: Determine Version Number
12 | id: determine_version_number
13 | run: echo "VERSION_NUMBER=$($env:GITHUB_REF_NAME.Replace('release/v', ''))" >> $env:GITHUB_OUTPUT
14 | - name: Determine Version Notes
15 | id: determine_release_notes
16 | run: |
17 | echo "VERSION_NOTES<> $env:GITHUB_OUTPUT
18 | echo $((Get-Content -Raw WireGuardServerForWindows\VersionInfo2.xml | Select-String "(?s)(.*)<\/VersionNotes>").Matches.Groups[1].Value) >> $env:GITHUB_OUTPUT
19 | echo "EOF" >> $env:GITHUB_OUTPUT
20 | - name: Setup MSBuild
21 | uses: microsoft/setup-msbuild@main
22 | - name: Download .NET Runtime
23 | run: Invoke-WebRequest https://download.visualstudio.microsoft.com/download/pr/27bcdd70-ce64-4049-ba24-2b14f9267729/d4a435e55182ce5424a7204c2cf2b3ea/windowsdesktop-runtime-8.0.11-win-x64.exe -OutFile Installer\windowsdesktop-runtime-8.0.11-win-x64.exe
24 | - name: Generate Release
25 | run: Installer\GenerateRelease.ps1
26 | - name: Publish Release
27 | uses: softprops/action-gh-release@master
28 | with:
29 | files: Installer/WS4WSetup-${{ steps.determine_version_number.outputs.VERSION_NUMBER }}.exe
30 | fail_on_unmatched_files: true
31 | tag_name: v${{ steps.determine_version_number.outputs.VERSION_NUMBER }}
32 | body: ${{ steps.determine_release_notes.outputs.VERSION_NOTES }}
33 | draft: true
34 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/SplashScreen.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ServerConfigurationEditorWindow.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/OpenClientConfigDirectorySubCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows.Input;
3 | using WgServerforWindows.Properties;
4 |
5 | namespace WgServerforWindows.Models
6 | {
7 | public class OpenClientConfigDirectorySubCommand : PrerequisiteItem
8 | {
9 | public OpenClientConfigDirectorySubCommand() : base
10 | (
11 | title: string.Empty,
12 | successMessage: string.Empty,
13 | errorMessage: string.Empty,
14 | resolveText: string.Empty,
15 | configureText: Resources.OpenClientConfigDirectoryConfigureText
16 | )
17 | {
18 | AppSettings.Instance.PropertyChanged += (_, args) =>
19 | {
20 | if (args.PropertyName == nameof(AppSettings.Instance.CustomClientConfigDirectory))
21 | {
22 | RaisePropertyChanged(nameof(SuccessMessage));
23 | RaisePropertyChanged(nameof(CanConfigure));
24 | }
25 | };
26 | }
27 |
28 | #region PrerequisiteItem members
29 |
30 | public override string SuccessMessage
31 | {
32 | get => string.Format(Resources.OpenClientConfigDirectorySuccessMessage, ClientConfigurationsPrerequisite.ClientConfigDirectory);
33 | set { }
34 | }
35 |
36 | public override void Configure()
37 | {
38 | if (CanConfigure)
39 | {
40 | WaitCursor.SetOverrideCursor(Cursors.Wait);
41 |
42 | Process.Start(new ProcessStartInfo
43 | {
44 | FileName = ClientConfigurationsPrerequisite.ClientConfigDirectory,
45 | UseShellExecute = true
46 | });
47 |
48 | WaitCursor.SetOverrideCursor(null);
49 | }
50 | }
51 |
52 | #endregion
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/OpenServerConfigDirectorySubCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows.Input;
3 | using WgServerforWindows.Properties;
4 |
5 | namespace WgServerforWindows.Models
6 | {
7 | public class OpenServerConfigDirectorySubCommand : PrerequisiteItem
8 | {
9 | public OpenServerConfigDirectorySubCommand() : base
10 | (
11 | title: string.Empty,
12 | successMessage: string.Empty,
13 | errorMessage: string.Empty,
14 | resolveText: string.Empty,
15 | configureText: Resources.OpenServerConfigDirectoryConfigureText
16 | )
17 | {
18 | AppSettings.Instance.PropertyChanged += (_, args) =>
19 | {
20 | if (args.PropertyName == nameof(AppSettings.Instance.CustomServerConfigDirectory))
21 | {
22 | RaisePropertyChanged(nameof(SuccessMessage));
23 | RaisePropertyChanged(nameof(CanConfigure));
24 | }
25 | };
26 | }
27 |
28 | #region PrerequisiteItem members
29 |
30 | public override string SuccessMessage
31 | {
32 | get => string.Format(Resources.OpenServerConfigDirectorySuccessMessage, ServerConfigurationPrerequisite.ServerConfigDirectory);
33 | set { }
34 | }
35 |
36 | public override void Configure()
37 | {
38 | if (CanConfigure)
39 | {
40 | WaitCursor.SetOverrideCursor(Cursors.Wait);
41 |
42 | Process.Start(new ProcessStartInfo
43 | {
44 | FileName = ServerConfigurationPrerequisite.ServerConfigDirectory,
45 | UseShellExecute = true
46 | });
47 |
48 | WaitCursor.SetOverrideCursor(null);
49 | }
50 | }
51 |
52 | #endregion
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/TunnelServiceNameSubCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using WgServerforWindows.Controls;
3 | using WgServerforWindows.Properties;
4 |
5 | namespace WgServerforWindows.Models
6 | {
7 | public class TunnelServiceNameSubCommand : PrerequisiteItem
8 | {
9 | public TunnelServiceNameSubCommand() : base(
10 | title: string.Empty,
11 | successMessage: string.Empty,
12 | errorMessage: string.Empty,
13 | resolveText: string.Empty,
14 | configureText: Resources.TunnelServiceNameSubCommandConfigureText
15 | )
16 | {
17 | }
18 |
19 | ///
20 | public override void Configure()
21 | {
22 | string backingObject = GlobalAppSettings.Instance.TunnelServiceName;
23 |
24 | var selectionWindowModel = new SelectionWindowModel
25 | {
26 | Title = Resources.CustomTunnelServiceNameSelectionTitle,
27 | Text = Resources.CustomTunnelServiceNameSelectionText,
28 | SelectedItem = new SelectionItem { BackingObject = backingObject },
29 | IsString = true,
30 | MinWidth = 350
31 | };
32 |
33 | selectionWindowModel.CanSelectFunc = () =>
34 | !string.IsNullOrWhiteSpace(selectionWindowModel.SelectedItem.BackingObject) &&
35 | selectionWindowModel.SelectedItem.BackingObject.All(c => char.IsLetterOrDigit(c) || c == '_');
36 |
37 | new SelectionWindow
38 | {
39 | DataContext = selectionWindowModel
40 | }.ShowDialog();
41 |
42 | if (selectionWindowModel.DialogResult == true)
43 | {
44 | GlobalAppSettings.Instance.TunnelServiceName = selectionWindowModel.SelectedItem.BackingObject;
45 | GlobalAppSettings.Instance.Save();
46 |
47 | Refresh();
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ClientConfigurationEditorWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using WgServerforWindows.Models;
5 |
6 | namespace WgServerforWindows.Controls
7 | {
8 | ///
9 | /// Interaction logic for ServerConfigurationEditor.xaml
10 | ///
11 | public partial class ClientConfigurationEditorWindow : Window
12 | {
13 | public ClientConfigurationEditorWindow()
14 | {
15 | InitializeComponent();
16 | }
17 |
18 | protected override void OnActivated(EventArgs e)
19 | {
20 | WaitCursor.SetOverrideCursor(null);
21 | }
22 |
23 | protected override void OnSourceInitialized(EventArgs e)
24 | {
25 | base.OnSourceInitialized(e);
26 |
27 | AppSettings.Instance.Tracker.Track(this);
28 | }
29 |
30 | #region Event handlers
31 |
32 | private void SaveButton_Click(object sender, RoutedEventArgs e)
33 | {
34 | DialogResult = true;
35 | Close();
36 | }
37 |
38 | private void CancelButton_Click(object sender, RoutedEventArgs e)
39 | {
40 | DialogResult = false;
41 | Close();
42 | }
43 |
44 | #endregion
45 |
46 | private void ExplorerSearchBox_SearchRequested(object sender, string e)
47 | {
48 | if (DataContext is ClientConfigurationList clientConfigurationList)
49 | {
50 | clientConfigurationList.List.ToList().ForEach(c => c.IsVisible = true);
51 |
52 | if (!string.IsNullOrWhiteSpace(e))
53 | {
54 | clientConfigurationList.List.Where(c => !c.Name.Contains(e, StringComparison.OrdinalIgnoreCase)).ToList().ForEach(c => c.IsVisible = false);
55 | }
56 |
57 | clientConfigurationList.RaisePropertyChanged(nameof(clientConfigurationList.CountString));
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ClientConfigurationList.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Linq;
3 | using System.Windows.Input;
4 | using System.Windows.Threading;
5 | using GalaSoft.MvvmLight;
6 | using GalaSoft.MvvmLight.Command;
7 | using WgServerforWindows.Properties;
8 |
9 | namespace WgServerforWindows.Models
10 | {
11 | public class ClientConfigurationList : ObservableObject
12 | {
13 | public ClientConfigurationList()
14 | {
15 | List.CollectionChanged += (_, __) =>
16 | {
17 | RaisePropertyChanged(nameof(CountString));
18 | };
19 | }
20 |
21 | public ObservableCollection List { get; } = new ObservableCollection();
22 |
23 | public string CountString => List.Any(c => !c.IsVisible)
24 | ? string.Format(Resources.FilteredClientCount, List.Count(c => c.IsVisible), List.Count)
25 | : string.Format(Resources.ClientCount, List.Count);
26 |
27 | public ICommand AddClientConfigurationCommand => _addClientConfigurationCommand ??= new RelayCommand(() =>
28 | {
29 | using (new WaitCursor(dispatcherPriority: DispatcherPriority.Render, restoreCursorToNull: true))
30 | {
31 | List.Add(new ClientConfiguration(this));
32 | }
33 | });
34 | private RelayCommand _addClientConfigurationCommand;
35 |
36 | public ICommand ExpandAllConfigurationsCommand => _expandAllConfigurationsCommand ??= new RelayCommand(() =>
37 | {
38 | List.ToList().ForEach(c => c.IsExpanded = true);
39 | });
40 | private RelayCommand _expandAllConfigurationsCommand;
41 |
42 | public ICommand CollapseAllConfigurationsCommand => _collapseAllConfigurationsCommand ??= new RelayCommand(() =>
43 | {
44 | List.ToList().ForEach(c => c.IsExpanded = false);
45 | });
46 | private RelayCommand _collapseAllConfigurationsCommand;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/WgServerforWindows/WaitCursor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Input;
4 | using System.Windows.Threading;
5 |
6 | namespace WgServerforWindows
7 | {
8 | // https://stackoverflow.com/a/3481274/4206279
9 | public class WaitCursor : IDisposable
10 | {
11 | private readonly Cursor _previousCursor;
12 | private readonly DispatcherPriority _dispatcherPriority;
13 |
14 | public WaitCursor(DispatcherPriority dispatcherPriority = DispatcherPriority.Normal, bool restoreCursorToNull = false)
15 | {
16 | _previousCursor = restoreCursorToNull ? null : Mouse.OverrideCursor;
17 | _dispatcherPriority = dispatcherPriority;
18 |
19 | SetOverrideCursor(Cursors.Wait);
20 | }
21 |
22 | public WaitCursor(DispatcherPriority dispatcherPriority = DispatcherPriority.Normal, Cursor restoreCursor = null)
23 | {
24 | _previousCursor = restoreCursor ?? Mouse.OverrideCursor;
25 | _dispatcherPriority = dispatcherPriority;
26 |
27 | SetOverrideCursor(Cursors.Wait);
28 | }
29 |
30 | #region IDisposable Members
31 |
32 | public void Dispose()
33 | {
34 | Application.Current?.Dispatcher.Invoke(() =>
35 | {
36 | SetOverrideCursor(_previousCursor);
37 | }, _dispatcherPriority);
38 | }
39 |
40 | #endregion
41 |
42 | ///
43 | /// Globally set the .
44 | ///
45 | ///
46 | public static void SetOverrideCursor(Cursor cursor)
47 | {
48 | if (!IgnoreOverrideCursor)
49 | {
50 | Mouse.OverrideCursor = cursor;
51 | }
52 | }
53 |
54 | ///
55 | /// Whether or not to allow to take effect.
56 | ///
57 | public static bool IgnoreOverrideCursor { get; set; }
58 | }
59 | }
--------------------------------------------------------------------------------
/WireGuardServerForWindows/AppUpdate.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/WireGuardExePrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using System.Windows.Input;
6 | using Flurl.Http;
7 | using WgAPI;
8 | using WgServerforWindows.Properties;
9 |
10 | namespace WgServerforWindows.Models
11 | {
12 | public class WireGuardExePrerequisite : PrerequisiteItem
13 | {
14 | public WireGuardExePrerequisite() : base
15 | (
16 | title: Resources.WireGuardExe,
17 | successMessage: Resources.WireGuardExeFound,
18 | errorMessage: Resources.WireGuardExeNotFound,
19 | resolveText: Resources.InstallWireGuard,
20 | configureText: Resources.UninstallWireGuard
21 | )
22 | {
23 | }
24 |
25 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
26 | {
27 | _wireGuardExe ??= new WireGuardExe();
28 | return _wireGuardExe.Exists;
29 | });
30 | private BooleanTimeCachedProperty _fulfilled;
31 |
32 | public override void Resolve()
33 | {
34 | WaitCursor.SetOverrideCursor(Cursors.Wait);
35 |
36 | string downloadPath = Path.GetTempPath();
37 | string downloadFileName = "wireguard.exe";
38 | wireGuardExeDownload.DownloadFileAsync(downloadPath, downloadFileName).GetAwaiter().GetResult();
39 | Process.Start(new ProcessStartInfo
40 | {
41 | FileName = Path.Combine(downloadPath, downloadFileName),
42 | Verb = "runas", // For elevation
43 | UseShellExecute = true // Must be true to use "runas"
44 | });
45 |
46 | Task.Run(WaitForFulfilled);
47 |
48 | WaitCursor.SetOverrideCursor(null);
49 | }
50 |
51 | public override void Configure()
52 | {
53 | WaitCursor.SetOverrideCursor(Cursors.Wait);
54 |
55 | _wireGuardExe.ExecuteCommand(new UninstallCommand());
56 | Refresh();
57 |
58 | WaitCursor.SetOverrideCursor(null);
59 | }
60 |
61 | private readonly string wireGuardExeDownload = @"https://download.wireguard.com/windows-client/wireguard-installer.exe";
62 | private WireGuardExe _wireGuardExe;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/BootTaskDelaySubCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using WgServerforWindows.Controls;
3 | using WgServerforWindows.Properties;
4 |
5 | namespace WgServerforWindows.Models
6 | {
7 | public class BootTaskDelaySubCommand : PrerequisiteItem
8 | {
9 | #region PrerequisiteItem members
10 |
11 | public BootTaskDelaySubCommand() : base
12 | (
13 | title: string.Empty,
14 | successMessage: Resources.BootTaskDelaySuccess,
15 | errorMessage: string.Empty,
16 | resolveText: string.Empty,
17 | configureText: Resources.BootTaskDelayConfigure
18 | )
19 | {
20 | }
21 |
22 | public override BooleanTimeCachedProperty Fulfilled { get; } = new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () => true);
23 |
24 | public override void Resolve()
25 | {
26 | throw new NotImplementedException();
27 | }
28 |
29 | public override void Configure()
30 | {
31 | DateTime backingObject = new DateTime(1, 1, 1,
32 | GlobalAppSettings.Instance.BootTaskDelay.Hours,
33 | GlobalAppSettings.Instance.BootTaskDelay.Minutes,
34 | GlobalAppSettings.Instance.BootTaskDelay.Seconds);
35 |
36 | var selectionWindowModel = new SelectionWindowModel
37 | {
38 | Title = Resources.BootDelaySelectionTitle,
39 | Text = Resources.BootDelaySelectionText,
40 | SelectedItem = new SelectionItem { BackingObject = backingObject },
41 | IsList = false,
42 | IsTimeSpan = true
43 | };
44 |
45 | new SelectionWindow
46 | {
47 | DataContext = selectionWindowModel
48 | }.ShowDialog();
49 |
50 | if (selectionWindowModel.DialogResult == true)
51 | {
52 | var timeSpan = new TimeSpan(
53 | selectionWindowModel.SelectedItem.BackingObject.Hour,
54 | selectionWindowModel.SelectedItem.BackingObject.Minute,
55 | selectionWindowModel.SelectedItem.BackingObject.Second);
56 |
57 | GlobalAppSettings.Instance.BootTaskDelay = timeSpan;
58 | GlobalAppSettings.Instance.Save();
59 | }
60 | }
61 |
62 | #endregion
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/WgServerforWindows/Converters/LastIndexVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Globalization;
4 | using System.Windows;
5 | using System.Windows.Data;
6 | using System.Windows.Markup;
7 |
8 | namespace WgServerforWindows.Converters
9 | {
10 | ///
11 | /// Allows hiding the last item in a list. The ItemsSource must be an .
12 | /// Since the converter needs both the whole list and the current item (to compare the indexes),
13 | /// and since a ConverterParameter can't be bound, use a multi-binding to send both pieces of relevant information.
14 | /// The item of interest must be first, and the list must be second.
15 | /// Optionally set the parameter to determine the visibility of the hidden item. This can be set in resources,
16 | /// or, since this converter supports MarkupExtension, it can be set inline.
17 | /// For example:
18 | ///
19 | ///
20 | ///
21 | ///
22 | ///
23 | ///
24 | ///
25 | ///
26 | ///
27 | public class LastIndexVisibilityConverter : MarkupExtension, IMultiValueConverter
28 | {
29 | #region IMultiValueConverter members
30 |
31 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
32 | {
33 | Visibility result = Visibility.Visible;
34 |
35 | if (values?.Length >= 2)
36 | {
37 | if ((values[1] as IList)?[^1] == values[0])
38 | {
39 | result = InvisibleMode;
40 | }
41 | }
42 |
43 | return result;
44 | }
45 |
46 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
47 | {
48 | throw new NotImplementedException();
49 | }
50 |
51 | #endregion
52 |
53 | #region MarkupExtension members
54 |
55 | public override object ProvideValue(IServiceProvider serviceProvider)
56 | {
57 | return this;
58 | }
59 |
60 | #endregion
61 |
62 | #region Public properties
63 |
64 | public Visibility InvisibleMode { get; set; } = Visibility.Collapsed;
65 |
66 | #endregion
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/WgServerforWindows/TimeCachedProperty.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WgServerforWindows
4 | {
5 | public class TimeCachedProperty
6 | {
7 | public TimeCachedProperty(TimeSpan cacheTime, Func calculateValueFunc)
8 | {
9 | _cacheTime = cacheTime;
10 | _calculateValueFunc = calculateValueFunc ?? throw new ArgumentNullException(nameof(calculateValueFunc));
11 | }
12 |
13 | public static implicit operator TValue(TimeCachedProperty timeCachedProperty) => timeCachedProperty.Value;
14 |
15 | public TValue Value
16 | {
17 | get
18 | {
19 | if (_lastAccessedTime + _cacheTime < DateTimeOffset.Now || _cachedValue is null)
20 | {
21 | _cachedValue = _calculateValueFunc();
22 | }
23 |
24 | _lastAccessedTime = DateTimeOffset.Now;
25 |
26 | return _cachedValue;
27 | }
28 | }
29 | private TValue _cachedValue;
30 |
31 | private readonly Func _calculateValueFunc;
32 | private readonly TimeSpan _cacheTime;
33 | private DateTimeOffset _lastAccessedTime;
34 | }
35 |
36 | // Implement some custom operators on Boolean type TimeCachedProperties
37 | // so that this type works well with CalcBinding and Expressions (which can't take advantage of the implicit constructor)
38 | public class BooleanTimeCachedProperty : TimeCachedProperty
39 | {
40 | public BooleanTimeCachedProperty(TimeSpan cacheTime, Func calculateValueFunc)
41 | : base(cacheTime, calculateValueFunc) { }
42 |
43 | public static bool operator !(BooleanTimeCachedProperty @this) => !@this.Value;
44 |
45 | public static bool operator true(BooleanTimeCachedProperty @this) => @this.Value;
46 |
47 | public static bool operator false(BooleanTimeCachedProperty @this) => !@this.Value;
48 |
49 | public static BooleanTimeCachedProperty operator &(BooleanTimeCachedProperty @this, BooleanTimeCachedProperty other) =>
50 | @this.Value && other.Value
51 | ? new BooleanTimeCachedProperty(TimeSpan.Zero, () => true)
52 | : new BooleanTimeCachedProperty(TimeSpan.Zero, () => false);
53 |
54 | public static BooleanTimeCachedProperty operator |(BooleanTimeCachedProperty @this, BooleanTimeCachedProperty other) =>
55 | @this.Value || other.Value
56 | ? new BooleanTimeCachedProperty(TimeSpan.Zero, () => true)
57 | : new BooleanTimeCachedProperty(TimeSpan.Zero, () => false);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/UnhandledErrorWindowModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Windows;
6 | using System.Windows.Input;
7 | using GalaSoft.MvvmLight;
8 | using GalaSoft.MvvmLight.Command;
9 | using WgServerforWindows.Properties;
10 |
11 | namespace WgServerforWindows.Models
12 | {
13 | public class UnhandledErrorWindowModel : ObservableObject
14 | {
15 | public string Title
16 | {
17 | get => _title;
18 | set => Set(nameof(Title), ref _title, value);
19 | }
20 | private string _title;
21 |
22 | public string Text
23 | {
24 | get => _text;
25 | set => Set(nameof(Text), ref _text, value);
26 | }
27 | private string _text;
28 |
29 | public Exception Exception
30 | {
31 | get => _exception;
32 | set => Set(nameof(Exception), ref _exception, value);
33 | }
34 | private Exception _exception;
35 |
36 | public string SecondaryButtonText
37 | {
38 | get => _secondaryButtonText;
39 | set => Set(nameof(SecondaryButtonText), ref _secondaryButtonText, value);
40 | }
41 | private string _secondaryButtonText = Resources.CopyDetails;
42 |
43 | public Action SecondaryButtonAction
44 | {
45 | get => _secondaryButtonAction ?? CopyExceptionToClipboard;
46 | set => _secondaryButtonAction = value;
47 |
48 | }
49 | private Action _secondaryButtonAction;
50 |
51 | public ICommand CopyErrorCommand => _copyErrorCommand ??= new RelayCommand(() => SecondaryButtonAction?.Invoke());
52 | private RelayCommand _copyErrorCommand;
53 |
54 | private void CopyExceptionToClipboard()
55 | {
56 | var exception = Exception;
57 | StringBuilder exceptionText = new StringBuilder();
58 | while (exception is { })
59 | {
60 | exceptionText.Append(exception);
61 | exceptionText.Append(Environment.NewLine);
62 | exceptionText.Append(Environment.NewLine);
63 | exception = exception.InnerException;
64 | }
65 |
66 | // This can help to alleviate issues opening the clipboard like CLIPBRD_E_CANT_OPEN
67 | // See: https://stackoverflow.com/a/69081
68 | foreach (var _ in Enumerable.Range(0, 10))
69 | {
70 | try
71 | {
72 | Clipboard.SetText(exceptionText.ToString());
73 | break;
74 | }
75 | catch { }
76 | Thread.Sleep(10);
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/UnhandledErrorWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
49 |
50 |
51 |
58 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Installer/UpdateVersions.ps1:
--------------------------------------------------------------------------------
1 | # This script is intended to be run from the root of the repo, like .\Installer\UpdateVersions.ps1
2 |
3 | $newVersion = $args[0]
4 | $versionNotes = $args[1]
5 |
6 | if ($args.count -eq 0) {
7 | $newVersion = Read-Host "Enter the new version number (without 'v' and without trailing '.0')"
8 | }
9 |
10 | # Directory.Build.props
11 | $directoryBuildPropsFile = Get-Content "Directory.Build.props"
12 | for ($i = 0; $i -lt $directoryBuildPropsFile.Length; $i += 1) {
13 | $line = $directoryBuildPropsFile[$i]
14 |
15 | if ($line -match "AssemblyVersion") {
16 | $directoryBuildPropsFile[$i] = " $($newVersion).0"
17 | }
18 |
19 | if ($line -match "FileVersion") {
20 | $directoryBuildPropsFile[$i] = " $($newVersion).0"
21 | }
22 |
23 | if ($line -match "InformationalVersion") {
24 | $directoryBuildPropsFile[$i] = " $($newVersion).0"
25 | }
26 | }
27 |
28 | Set-Content "Directory.Build.props" $directoryBuildPropsFile
29 |
30 | # WS4WSetupScript.iss
31 | $setupScript = Get-Content "Installer\WS4WSetupScript.iss"
32 | for ($i = 0; $i -lt $setupScript.Length; $i += 1) {
33 | $line = $setupScript[$i]
34 |
35 | if ($line -match "#define MyAppVersion") {
36 | $setupScript[$i] = "#define MyAppVersion ""$($newVersion)"""
37 | }
38 | }
39 |
40 | Set-Content "Installer\WS4WSetupScript.iss" $setupScript
41 |
42 | # VersionInfo2.xml
43 | $versionInfo = Get-Content "WireGuardServerForWindows\VersionInfo2.xml"
44 | for ($i = 0; $i -lt $versionInfo.Length; $i += 1) {
45 | $line = $versionInfo[$i]
46 |
47 | if ($line -match "") {
48 | $versionInfo[$i] = " $($newVersion).0"
49 | }
50 |
51 | if ($line -match "ReleaseDate") {
52 | $versionInfo[$i] = " $(Get-Date -Format "yyyy-MM-dd")"
53 | }
54 |
55 | if ($line -match "DownloadLink") {
56 | $versionInfo[$i] = " https://github.com/micahmo/WgServerforWindows/releases/download/v$($newVersion)/WS4WSetup-$($newVersion).exe"
57 | }
58 |
59 | if ($line -match "DownloadFileName") {
60 | $versionInfo[$i] = " WS4WSetup-$($newVersion).exe"
61 | }
62 |
63 | if ($line -match "$($versionNotes)"
73 | }
74 | }
75 |
76 | Set-Content "WireGuardServerForWindows\VersionInfo2.xml" $versionInfo
77 |
78 | Write-Host -ForegroundColor Red "Don't forget to update VersionNotes!"
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ConfigurationPropertyAction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Input;
4 | using GalaSoft.MvvmLight;
5 | using GalaSoft.MvvmLight.Command;
6 | using WgServerforWindows.Converters;
7 |
8 | namespace WgServerforWindows.Models
9 | {
10 | public class ConfigurationPropertyAction : ObservableObject
11 | {
12 | public ConfigurationPropertyAction(ConfigurationBase parentConfiguration) =>
13 | _parentConfiguration = parentConfiguration;
14 |
15 | public string Name
16 | {
17 | get => _name;
18 | set => Set(nameof(Name), ref _name, value);
19 | }
20 | private string _name;
21 |
22 | public string Description
23 | {
24 | get => _description ?? new ResourceStringConverter().Convert(Name, typeof(string), null, CultureInfo.CurrentCulture) as string;
25 | set => _description = value;
26 | }
27 | private string _description;
28 |
29 | public ConfigurationProperty DependentProperty
30 | {
31 | get => _dependentProperty;
32 | set
33 | {
34 | _dependentProperty = value;
35 |
36 | if (_dependentProperty is { })
37 | {
38 | _dependentProperty.PropertyChanged += (_, __) =>
39 | {
40 | RaisePropertyChanged(nameof(DependencySatisfied));
41 | };
42 | }
43 | }
44 | }
45 | private ConfigurationProperty _dependentProperty;
46 |
47 | public bool DependencySatisfied => DependencySatisfiedFunc?.Invoke(_dependentProperty) ?? true;
48 |
49 | public Func DependencySatisfiedFunc
50 | {
51 | get => _dependencySatisfiedFunc;
52 | set
53 | {
54 | _dependencySatisfiedFunc = value;
55 | RaisePropertyChanged(nameof(DependencySatisfied));
56 | }
57 | }
58 | private Func _dependencySatisfiedFunc;
59 |
60 | public Action Action { get; set; }
61 |
62 | ///
63 | /// An action to be invoked after the configuration has been loaded
64 | ///
65 | public Action OnLoadAction { get; set; }
66 |
67 | public ICommand ExecuteActionCommand => _executeActionCommand ??= new RelayCommand(() =>
68 | {
69 | Action?.Invoke(_parentConfiguration, null);
70 | });
71 | private RelayCommand _executeActionCommand;
72 |
73 | #region Private fields
74 |
75 | private readonly ConfigurationBase _parentConfiguration;
76 |
77 | #endregion
78 | }
79 |
80 | public class ConfigurationPropertyValidation
81 | {
82 | public Func Validate { get; set; }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ChangeClientConfigDirectorySubCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows.Input;
4 | using Microsoft.WindowsAPICodePack.Dialogs;
5 | using WgServerforWindows.Properties;
6 |
7 | namespace WgServerforWindows.Models
8 | {
9 | public class ChangeClientConfigDirectorySubCommand : PrerequisiteItem
10 | {
11 | public ChangeClientConfigDirectorySubCommand() : base
12 | (
13 | title: string.Empty,
14 | successMessage: string.Empty,
15 | errorMessage: string.Empty,
16 | resolveText: string.Empty,
17 | configureText: Resources.ChangeClientConfigDirectoryConfigureText
18 | )
19 | {
20 | AppSettings.Instance.PropertyChanged += (_, args) =>
21 | {
22 | if (args.PropertyName == nameof(AppSettings.Instance.CustomClientConfigDirectory))
23 | {
24 | RaisePropertyChanged(nameof(SuccessMessage));
25 | }
26 | };
27 | }
28 |
29 | #region PrerequisiteItem members
30 |
31 | public override string SuccessMessage
32 | {
33 | get => string.Format(Resources.ChangeClientConfigDirectorySuccessMessage, ClientConfigurationsPrerequisite.ClientConfigDirectory);
34 | set { }
35 | }
36 |
37 | public override void Configure()
38 | {
39 | using CommonOpenFileDialog commonOpenFileDialog = new CommonOpenFileDialog
40 | {
41 | IsFolderPicker = true,
42 | InitialDirectory = ClientConfigurationsPrerequisite.ClientConfigDirectory
43 | };
44 |
45 | if (commonOpenFileDialog.ShowDialog() == CommonFileDialogResult.Ok
46 | && Directory.Exists(commonOpenFileDialog.FileName)
47 | && !commonOpenFileDialog.FileName.Equals(ClientConfigurationsPrerequisite.ClientConfigDirectory, StringComparison.OrdinalIgnoreCase))
48 | {
49 | WaitCursor.SetOverrideCursor(Cursors.Wait);
50 |
51 | if (Directory.Exists(ClientConfigurationsPrerequisite.ClientWGDirectory))
52 | {
53 | Directory.Move(ClientConfigurationsPrerequisite.ClientWGDirectory, Path.Combine(commonOpenFileDialog.FileName, Path.GetFileName(ClientConfigurationsPrerequisite.ClientWGDirectory)));
54 | }
55 |
56 | if (Directory.Exists(ClientConfigurationsPrerequisite.ClientDataDirectory))
57 | {
58 | Directory.Move(ClientConfigurationsPrerequisite.ClientDataDirectory, Path.Combine(commonOpenFileDialog.FileName, Path.GetFileName(ClientConfigurationsPrerequisite.ClientDataDirectory)));
59 | }
60 |
61 | AppSettings.Instance.CustomClientConfigDirectory = commonOpenFileDialog.FileName;
62 | AppSettings.Instance.Save();
63 |
64 | WaitCursor.SetOverrideCursor(null);
65 | }
66 | }
67 |
68 | #endregion
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ChangeServerConfigDirectorySubCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows.Input;
4 | using Microsoft.WindowsAPICodePack.Dialogs;
5 | using WgServerforWindows.Properties;
6 |
7 | namespace WgServerforWindows.Models
8 | {
9 | public class ChangeServerConfigDirectorySubCommand : PrerequisiteItem
10 | {
11 | public ChangeServerConfigDirectorySubCommand() : base
12 | (
13 | title: string.Empty,
14 | successMessage: string.Empty,
15 | errorMessage: string.Empty,
16 | resolveText: string.Empty,
17 | configureText: Resources.ChangeServerConfigDirectoryConfigureText
18 | )
19 | {
20 | AppSettings.Instance.PropertyChanged += (_, args) =>
21 | {
22 | if (args.PropertyName == nameof(AppSettings.Instance.CustomServerConfigDirectory))
23 | {
24 | RaisePropertyChanged(nameof(SuccessMessage));
25 | }
26 | };
27 | }
28 |
29 | #region PrerequisiteItem members
30 |
31 | public override string SuccessMessage
32 | {
33 | get => string.Format(Resources.ChangeServerConfigDirectorySuccessMessage, ServerConfigurationPrerequisite.ServerConfigDirectory);
34 | set { }
35 | }
36 |
37 | public override void Configure()
38 | {
39 | using CommonOpenFileDialog commonOpenFileDialog = new CommonOpenFileDialog
40 | {
41 | IsFolderPicker = true,
42 | InitialDirectory = ServerConfigurationPrerequisite.ServerConfigDirectory
43 | };
44 |
45 | if (commonOpenFileDialog.ShowDialog() == CommonFileDialogResult.Ok
46 | && Directory.Exists(commonOpenFileDialog.FileName)
47 | && !commonOpenFileDialog.FileName.Equals(ServerConfigurationPrerequisite.ServerConfigDirectory, StringComparison.OrdinalIgnoreCase))
48 | {
49 | WaitCursor.SetOverrideCursor(Cursors.Wait);
50 |
51 | if (Directory.Exists(ServerConfigurationPrerequisite.ServerWGDirectory))
52 | {
53 | Directory.Move(ServerConfigurationPrerequisite.ServerWGDirectory, Path.Combine(commonOpenFileDialog.FileName, Path.GetFileName(ServerConfigurationPrerequisite.ServerWGDirectory)));
54 | }
55 |
56 | if (Directory.Exists(ServerConfigurationPrerequisite.ServerDataDirectory))
57 | {
58 | Directory.Move(ServerConfigurationPrerequisite.ServerDataDirectory, Path.Combine(commonOpenFileDialog.FileName, Path.GetFileName(ServerConfigurationPrerequisite.ServerDataDirectory)));
59 | }
60 |
61 | AppSettings.Instance.CustomServerConfigDirectory = commonOpenFileDialog.FileName;
62 | AppSettings.Instance.Save();
63 |
64 | WaitCursor.SetOverrideCursor(null);
65 | }
66 | }
67 |
68 | #endregion
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/PrivateNetworkTaskSubCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Windows.Input;
5 | using Microsoft.Win32.TaskScheduler;
6 | using WgServerforWindows.Cli.Options;
7 | using WgServerforWindows.Properties;
8 |
9 | namespace WgServerforWindows.Models
10 | {
11 | public class PrivateNetworkTaskSubCommand : PrerequisiteItem
12 | {
13 | public PrivateNetworkTaskSubCommand() : base
14 | (
15 | string.Empty,
16 | successMessage: Resources.PrivateNetworkTaskSubCommandSuccessMessage,
17 | errorMessage: Resources.PrivateNetworkTaskSubCommandErrorMessage,
18 | resolveText: Resources.PrivateNetworkTaskSubCommandResolveText,
19 | configureText: Resources.PrivateNetworkTaskSubCommandConfigureText
20 | )
21 | {
22 | }
23 |
24 | #region PrerequisiteItem members
25 |
26 | ///
27 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
28 | TaskService.Instance.FindTask(_privateNetworkTaskUniqueName) is { Enabled: true } task
29 | && task.Definition.Triggers.FirstOrDefault() is BootTrigger
30 | && task.Definition.Actions.FirstOrDefault() is ExecAction action
31 | && action.Path == Path.Combine(AppContext.BaseDirectory, "ws4w.exe")
32 | && action.Arguments.StartsWith(typeof(PrivateNetworkCommand).GetVerb()));
33 | private BooleanTimeCachedProperty _fulfilled;
34 |
35 | ///
36 | public override void Resolve()
37 | {
38 | WaitCursor.SetOverrideCursor(Cursors.Wait);
39 |
40 | // Create/update a Scheduled Task that sets the Private network category on boot.
41 | TaskDefinition td = TaskService.Instance.NewTask();
42 | td.Actions.Add(new ExecAction(Path.Combine(AppContext.BaseDirectory, "ws4w.exe"), typeof(PrivateNetworkCommand).GetVerb()));
43 | td.Triggers.Add(new BootTrigger { Delay = GlobalAppSettings.Instance.BootTaskDelay });
44 | TaskService.Instance.RootFolder.RegisterTaskDefinition(_privateNetworkTaskUniqueName, td, TaskCreation.CreateOrUpdate, "SYSTEM", null, TaskLogonType.ServiceAccount);
45 |
46 | Refresh();
47 |
48 | WaitCursor.SetOverrideCursor(null);
49 | }
50 |
51 | public override void Configure()
52 | {
53 | WaitCursor.SetOverrideCursor(Cursors.Wait);
54 |
55 | // Disable the task
56 | if (TaskService.Instance.FindTask(_privateNetworkTaskUniqueName) is { } task)
57 | {
58 | task.Enabled = false;
59 | }
60 |
61 | Refresh();
62 |
63 | WaitCursor.SetOverrideCursor(null);
64 | }
65 |
66 | #endregion
67 |
68 | #region Private fields
69 |
70 | private readonly string _privateNetworkTaskUniqueName = "WS4W Private Network (bc87228e-afdb-4815-8786-b5934bcf53e6)";
71 |
72 | #endregion
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/WgServerforWindows/Properties/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
59 |
60 |
61 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/WgServerforWindowsCli/Properties/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
59 |
60 |
61 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/NewNetIpAddressTaskSubCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Windows.Input;
5 | using Microsoft.Win32.TaskScheduler;
6 | using WgServerforWindows.Cli.Options;
7 | using WgServerforWindows.Properties;
8 |
9 | namespace WgServerforWindows.Models
10 | {
11 | public class NewNetIpAddressTaskSubCommand : PrerequisiteItem
12 | {
13 | public NewNetIpAddressTaskSubCommand() : base
14 | (
15 | string.Empty,
16 | successMessage: Resources.NewNetIpAddressTaskSubCommandSuccessMessage,
17 | errorMessage: Resources.NewNetIpAddressTaskSubCommandErrorMessage,
18 | resolveText: Resources.NewNetIpAddressTaskSubCommandResolveText,
19 | configureText: Resources.NewNetIpAddressTaskSubCommandConfigureText
20 | )
21 | {
22 | }
23 |
24 | #region PrerequisiteItem members
25 |
26 | ///
27 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
28 | TaskService.Instance.FindTask(_netIpAddressTaskUniqueName) is { Enabled: true } task
29 | && task.Definition.Triggers.FirstOrDefault() is BootTrigger
30 | && task.Definition.Actions.FirstOrDefault() is ExecAction action
31 | && action.Path == Path.Combine(AppContext.BaseDirectory, "ws4w.exe")
32 | && action.Arguments.StartsWith(typeof(SetNetIpAddressCommand).GetVerb()));
33 | private BooleanTimeCachedProperty _fulfilled;
34 |
35 | ///
36 | public override void Resolve()
37 | {
38 | Resolve(default);
39 | }
40 |
41 | public void Resolve(string serverDataPath)
42 | {
43 | WaitCursor.SetOverrideCursor(Cursors.Wait);
44 |
45 | // Create/update a Scheduled Task that sets the NetIPAddress on boot.
46 | TaskDefinition td = TaskService.Instance.NewTask();
47 | td.Actions.Add(new ExecAction(Path.Combine(AppContext.BaseDirectory, "ws4w.exe"), $"{typeof(SetNetIpAddressCommand).GetVerb()} --{typeof(SetNetIpAddressCommand).GetOption(nameof(SetNetIpAddressCommand.ServerDataPath))} \"{serverDataPath ?? ServerConfigurationPrerequisite.ServerDataPath}\""));
48 | td.Triggers.Add(new BootTrigger { Delay = GlobalAppSettings.Instance.BootTaskDelay + TimeSpan.FromSeconds(15) });
49 | TaskService.Instance.RootFolder.RegisterTaskDefinition(_netIpAddressTaskUniqueName, td, TaskCreation.CreateOrUpdate, "SYSTEM", null, TaskLogonType.ServiceAccount);
50 |
51 | Refresh();
52 |
53 | WaitCursor.SetOverrideCursor(null);
54 | }
55 |
56 | public override void Configure()
57 | {
58 | WaitCursor.SetOverrideCursor(Cursors.Wait);
59 |
60 | // Disable the task
61 | if (TaskService.Instance.FindTask(_netIpAddressTaskUniqueName) is { } task)
62 | {
63 | task.Enabled = false;
64 | }
65 |
66 | Refresh();
67 |
68 | WaitCursor.SetOverrideCursor(null);
69 | }
70 |
71 | #endregion
72 |
73 | #region Private fields
74 |
75 | private readonly string _netIpAddressTaskUniqueName = "WS4W Set NetIPAddress (1048541f-d027-4a97-842d-ca331c3d03a9)";
76 |
77 | #endregion
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/GlobalAppSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using GalaSoft.MvvmLight;
4 | using Jot;
5 | using Jot.Storage;
6 |
7 | namespace WgServerforWindows.Models
8 | {
9 | ///
10 | /// Defines system-wide, application-wide settings which will be persisted across sessions
11 | ///
12 | internal class GlobalAppSettings : ObservableObject
13 | {
14 | #region Singleton member
15 |
16 | ///
17 | /// Singleton instance
18 | ///
19 | public static GlobalAppSettings Instance { get; } = new GlobalAppSettings();
20 |
21 | #endregion
22 |
23 | #region Private constructor
24 |
25 | ///
26 | /// Constructor
27 | ///
28 | private GlobalAppSettings()
29 | {
30 | // Set up AppSettings tracking
31 | Tracker.Configure()
32 | .Property(a => a.BootTaskDelay)
33 | .Property(a => a.CustomNetNatRange)
34 | .Property(a => a.TunnelServiceName)
35 | .Property(a => a.DoNotShowTemporaryProfileWarning)
36 | .Track(this);
37 | }
38 |
39 | #endregion
40 |
41 | #region Public methods
42 |
43 | public void Save()
44 | {
45 | Tracker.Persist(this);
46 | }
47 |
48 | #endregion
49 |
50 | #region Public properties
51 |
52 | ///
53 | /// Boot task delay time
54 | ///
55 | public TimeSpan BootTaskDelay
56 | {
57 | get => _bootTaskDelay;
58 | set => Set(nameof(BootTaskDelay), ref _bootTaskDelay, value);
59 | }
60 | private TimeSpan _bootTaskDelay;
61 |
62 | public string CustomNetNatRange
63 | {
64 | get => _customNetNatRange;
65 | set => Set(nameof(CustomNetNatRange), ref _customNetNatRange, value);
66 | }
67 | private string _customNetNatRange;
68 |
69 | ///
70 | /// Describes the name of the tunnel service used by WireGuard, defaults to wg_server
71 | ///
72 | public string TunnelServiceName
73 | {
74 | get => _tunnelServiceName;
75 | set => Set(nameof(TunnelServiceName), ref _tunnelServiceName, value);
76 | }
77 | private string _tunnelServiceName = "wg_server";
78 |
79 | ///
80 | /// Set this to `true` if the user does NOT want to see the temporary profile warning.
81 | /// The default is `false` meaning they CAN see the warning.
82 | ///
83 | public bool DoNotShowTemporaryProfileWarning
84 | {
85 | get => _doNotShowTemporaryProfileWarning;
86 | set => Set(nameof(DoNotShowTemporaryProfileWarning), ref _doNotShowTemporaryProfileWarning, value);
87 | }
88 | private bool _doNotShowTemporaryProfileWarning;
89 |
90 | ///
91 | /// The public tracker instance located in Public\Documents. Can be used to track things other than the .
92 | ///
93 | public Tracker Tracker { get; } = new Tracker(new JsonFileStore(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments), "WS4W")));
94 |
95 | #endregion
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/AppSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Windows;
5 | using GalaSoft.MvvmLight;
6 | using Jot;
7 | using Jot.Storage;
8 |
9 | namespace WgServerforWindows.Models
10 | {
11 | ///
12 | /// Defines application-wide settings which will be persisted across sessions
13 | ///
14 | internal class AppSettings : ObservableObject
15 | {
16 | #region Singleton member
17 |
18 | ///
19 | /// Singleton instance
20 | ///
21 | public static AppSettings Instance { get; } = new AppSettings();
22 |
23 | #endregion
24 |
25 | #region Private constructor
26 |
27 | ///
28 | /// Constructor
29 | ///
30 | private AppSettings()
31 | {
32 | // Set up Window tracking
33 | Tracker.Configure()
34 | .Id(w => w.Name, new Size(SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight))
35 | .Properties(w => new { w.Top, w.Width, w.Height, w.Left, w.WindowState })
36 | .PersistOn(nameof(Window.Closing))
37 | .StopTrackingOn(nameof(Window.Closing))
38 | .WhenPersistingProperty((w, p) => p.Cancel = p.Property == nameof(w.WindowState) && w.WindowState == WindowState.Minimized);
39 | }
40 |
41 | #endregion
42 |
43 | #region Public methods
44 |
45 | public void Load()
46 | {
47 | // Set up AppSettings tracking
48 | Tracker.Configure()
49 | .Property(a => a.CustomServerConfigDirectory)
50 | .Property(a => a.CustomClientConfigDirectory)
51 | .Property(a => a.ClientConfigurationExpansionStates)
52 | .Track(this);
53 | }
54 |
55 | public void Save()
56 | {
57 | Tracker.Persist(this);
58 | }
59 |
60 | #endregion
61 |
62 | #region Public properties
63 |
64 | ///
65 | /// The parent directory of the server configuration files
66 | ///
67 | public string CustomServerConfigDirectory
68 | {
69 | get => _customServerConfigDirectory;
70 | set => Set(nameof(CustomServerConfigDirectory), ref _customServerConfigDirectory, value);
71 | }
72 | private string _customServerConfigDirectory;
73 |
74 | ///
75 | /// The parent directory of the client configuration files
76 | ///
77 | public string CustomClientConfigDirectory
78 | {
79 | get => _customClientConfigDirectory;
80 | set => Set(nameof(CustomClientConfigDirectory), ref _customClientConfigDirectory, value);
81 | }
82 | private string _customClientConfigDirectory;
83 |
84 | ///
85 | /// Tracks whether each client configuration is expanded in the UI or not
86 | ///
87 | public Dictionary ClientConfigurationExpansionStates = new Dictionary();
88 |
89 | ///
90 | /// The public tracker instance. Can be used to track things other than the .
91 | ///
92 | public Tracker Tracker { get; } = new Tracker(new JsonFileStore(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WS4W")));
93 |
94 | #endregion
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/SelectionWindowModel.cs:
--------------------------------------------------------------------------------
1 | using GalaSoft.MvvmLight;
2 | using GalaSoft.MvvmLight.Command;
3 | using System;
4 | using System.Collections.ObjectModel;
5 | using System.Windows.Input;
6 |
7 | namespace WgServerforWindows.Models
8 | {
9 | public class SelectionWindowModel : ObservableObject
10 | {
11 | public string Title
12 | {
13 | get => _title;
14 | set => Set(nameof(Title), ref _title, value);
15 | }
16 | private string _title;
17 |
18 | public string Text
19 | {
20 | get => _text;
21 | set => Set(nameof(Text), ref _text, value);
22 | }
23 | private string _text;
24 |
25 | public string ValidationError
26 | {
27 | get => _validationError;
28 | set => Set(nameof(ValidationError), ref _validationError, value);
29 | }
30 | private string _validationError;
31 |
32 | public double MinWidth
33 | {
34 | get => _minWidth;
35 | set => Set(nameof(MinWidth), ref _minWidth, value);
36 | }
37 | private double _minWidth;
38 |
39 | public ObservableCollection> Items { get; } = new ObservableCollection>();
40 |
41 | public SelectionItem SelectedItem
42 | {
43 | get => _selectedItem;
44 | set
45 | {
46 | Set(nameof(SelectedItem), ref _selectedItem, value);
47 | RaisePropertyChanged(nameof(CanSelect));
48 |
49 | SelectedItem.PropertyChanged += (_, args) =>
50 | {
51 | if (args.PropertyName == nameof(SelectedItem.BackingObject))
52 | {
53 | RaisePropertyChanged(nameof(CanSelect));
54 | }
55 | };
56 | }
57 | }
58 | private SelectionItem _selectedItem;
59 |
60 | public bool IsList { get; set; } = true;
61 |
62 | public bool IsTimeSpan { get; set; }
63 |
64 | public bool IsString { get; set; }
65 |
66 | public bool? DialogResult { get; private set; }
67 |
68 | public ICommand CancelCommand => _cancelCommand ??= new RelayCommand(() =>
69 | {
70 | DialogResult = false;
71 | });
72 | private RelayCommand _cancelCommand;
73 |
74 | public ICommand SelectCommand => _selectCommand ??= new RelayCommand(() =>
75 | {
76 | DialogResult = true;
77 | });
78 | private RelayCommand _selectCommand;
79 |
80 | public bool CanSelect => CanSelectFunc?.Invoke() ?? SelectedItem is { };
81 |
82 | public Func CanSelectFunc { get; set; }
83 | }
84 |
85 | public class SelectionItem : ObservableObject
86 | {
87 | public string DisplayText
88 | {
89 | get => _displayText;
90 | set => Set(nameof(DisplayText), ref _displayText, value);
91 | }
92 | private string _displayText;
93 |
94 | public string Description
95 | {
96 | get => _description;
97 | set => Set(nameof(Description), ref _description, value);
98 | }
99 | private string _description;
100 | }
101 |
102 | public class SelectionItem : SelectionItem
103 | {
104 | public T BackingObject
105 | {
106 | get => _backingObject;
107 | set => Set(nameof(BackingObject), ref _backingObject, value);
108 | }
109 | private T _backingObject;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/WgServerforWindows/WgServerforWindows.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net80-windows
6 | true
7 | Images\logo.ico
8 | Properties\app.manifest
9 | WS4W
10 |
11 |
12 |
13 |
14 | $(NoWarn);NU1701;NETSDK1137
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 0
28 | 1
29 | 43e734ca-043d-4a70-9a2c-a8f254063d91
30 | 0
31 | tlbimp
32 | false
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | lib\ExplorerSearchBox.dll
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | True
78 | True
79 | Resources.resx
80 |
81 |
82 |
83 |
84 |
85 | PublicResXFileCodeGenerator
86 | Resources.Designer.cs
87 | WgServerforWindows.Properties
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/NetNatRangeSubCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using SharpConfig;
3 | using WgServerforWindows.Controls;
4 | using WgServerforWindows.Properties;
5 |
6 | namespace WgServerforWindows.Models
7 | {
8 | public class NetNatRangeSubCommand : PrerequisiteItem
9 | {
10 | public NetNatRangeSubCommand() : base
11 | (
12 | title: string.Empty,
13 | successMessage: Resources.NetNatRangeSubCommandSuccessMessage,
14 | errorMessage: string.Empty,
15 | resolveText: string.Empty,
16 | configureText: Resources.NewNetNatRangeSubCommandConfigureText
17 | )
18 | {
19 | }
20 |
21 | public override void Configure()
22 | {
23 | var serverConfiguration = new ServerConfiguration().Load(Configuration.LoadFromFile(ServerConfigurationPrerequisite.ServerDataPath));
24 |
25 | string networkRange = GlobalAppSettings.Instance.CustomNetNatRange;
26 |
27 | var selectionWindowModel = new SelectionWindowModel
28 | {
29 | Title = Resources.NetNatRangeSelectionTitle,
30 | Text = Resources.NetNatRangeSelectionText,
31 | SelectedItem = new SelectionItem { BackingObject = networkRange },
32 | IsList = false,
33 | IsString = true,
34 | MinWidth = 350
35 | };
36 |
37 | selectionWindowModel.CanSelectFunc = () =>
38 | {
39 | if (!string.IsNullOrWhiteSpace(selectionWindowModel.SelectedItem.BackingObject))
40 | {
41 | if (!IPNetwork2.TryParse(selectionWindowModel.SelectedItem.BackingObject, out _))
42 | {
43 | selectionWindowModel.ValidationError = Resources.NetworkAddressValidationError;
44 | return false;
45 | }
46 | // IPNetwork2.TryParse recognizes single IP addresses as CIDR (with 8 mask).
47 | // This is not good, because we want an explicit CIDR for the server.
48 | // Therefore, if IPNetwork2.TryParse succeeds, and IPAddress.TryParse also succeeds, we have a problem.
49 | if (IPAddress.TryParse(selectionWindowModel.SelectedItem.BackingObject, out _))
50 | {
51 | // This is just a regular address. We want CIDR.
52 | selectionWindowModel.ValidationError = Resources.NetworkAddressValidationError;
53 | return false;
54 | }
55 |
56 | // Ensure that this range contains the server range
57 | IPNetwork2 netNatRange = IPNetwork2.Parse(selectionWindowModel.SelectedItem.BackingObject);
58 | IPNetwork2 serverRange = IPNetwork2.Parse(serverConfiguration.AddressProperty.Value);
59 | if (!netNatRange.Contains(serverRange))
60 | {
61 | selectionWindowModel.ValidationError = string.Format(Resources.NetNatRangeValidationError, serverRange);
62 | return false;
63 | }
64 |
65 | // It passed, so we're good.
66 | selectionWindowModel.ValidationError = null;
67 | return true;
68 | }
69 |
70 | // It's empty, which is fine.
71 | selectionWindowModel.ValidationError = null;
72 | return true;
73 | };
74 |
75 | new SelectionWindow
76 | {
77 | DataContext = selectionWindowModel
78 | }.ShowDialog();
79 |
80 | if (selectionWindowModel.DialogResult == true)
81 | {
82 | GlobalAppSettings.Instance.CustomNetNatRange = selectionWindowModel.SelectedItem.BackingObject;
83 | GlobalAppSettings.Instance.Save();
84 |
85 | Refresh();
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/WgServerforWindowsCli/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace WgServerforWindows.Cli.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WgServerforWindows.Cli.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Command did not complete successfully..
65 | ///
66 | public static string CommandFailed {
67 | get {
68 | return ResourceManager.GetString("CommandFailed", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Command executed successfully..
74 | ///
75 | public static string CommandSucceeded {
76 | get {
77 | return ResourceManager.GetString("CommandSucceeded", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Error: ws4w.exe must be run as Administrator..
83 | ///
84 | public static string MustRunAsAdmin {
85 | get {
86 | return ResourceManager.GetString("MustRunAsAdmin", resourceCulture);
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/WgServerforWindows.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.12.35527.113 d17.12
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WgServerforWindows", "WgServerforWindows\WgServerforWindows.csproj", "{BF1E275D-A372-4C32-A118-EF85A28B60C8}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WgAPI", "WgAPI\WgAPI.csproj", "{16FC6541-F37F-492B-88AB-ABF73AA7BB1E}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42A258C8-5F33-4D0D-B0CF-0861EB334E97}"
11 | ProjectSection(SolutionItems) = preProject
12 | .gitignore = .gitignore
13 | .github\workflows\create_release_branch.yml = .github\workflows\create_release_branch.yml
14 | Directory.Build.props = Directory.Build.props
15 | Installer\GenerateRelease.ps1 = Installer\GenerateRelease.ps1
16 | .github\workflows\generate_release.yml = .github\workflows\generate_release.yml
17 | README.md = README.md
18 | Installer\README.md = Installer\README.md
19 | .github\workflows\restore_build_test.yml = .github\workflows\restore_build_test.yml
20 | Installer\UpdateVersions.ps1 = Installer\UpdateVersions.ps1
21 | .github\workflows\update_versions.yml = .github\workflows\update_versions.yml
22 | Installer\WS4WSetupScript.iss = Installer\WS4WSetupScript.iss
23 | EndProjectSection
24 | EndProject
25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WgServerforWindows.Cli", "WgServerforWindowsCli\WgServerforWindows.Cli.csproj", "{94DDF147-2EA8-45F4-B843-9470EA3C38F5}"
26 | EndProject
27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WgServerforWindows.Cli.Options", "WgServerforWindows.Cli.Options\WgServerforWindows.Cli.Options.csproj", "{841E987C-04F2-4EC8-A66B-DEE8344D3211}"
28 | EndProject
29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WgServerforWindows.Tests", "WgServerforWindows.Tests\WgServerforWindows.Tests.csproj", "{BE244BB9-CE9D-44BE-9AF4-81705554908F}"
30 | EndProject
31 | Global
32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
33 | Debug|Any CPU = Debug|Any CPU
34 | Release|Any CPU = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
37 | {BF1E275D-A372-4C32-A118-EF85A28B60C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {BF1E275D-A372-4C32-A118-EF85A28B60C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {BF1E275D-A372-4C32-A118-EF85A28B60C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {BF1E275D-A372-4C32-A118-EF85A28B60C8}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {16FC6541-F37F-492B-88AB-ABF73AA7BB1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {16FC6541-F37F-492B-88AB-ABF73AA7BB1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {16FC6541-F37F-492B-88AB-ABF73AA7BB1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {16FC6541-F37F-492B-88AB-ABF73AA7BB1E}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {94DDF147-2EA8-45F4-B843-9470EA3C38F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {94DDF147-2EA8-45F4-B843-9470EA3C38F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {94DDF147-2EA8-45F4-B843-9470EA3C38F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {94DDF147-2EA8-45F4-B843-9470EA3C38F5}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {841E987C-04F2-4EC8-A66B-DEE8344D3211}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {841E987C-04F2-4EC8-A66B-DEE8344D3211}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {841E987C-04F2-4EC8-A66B-DEE8344D3211}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {841E987C-04F2-4EC8-A66B-DEE8344D3211}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {BE244BB9-CE9D-44BE-9AF4-81705554908F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {BE244BB9-CE9D-44BE-9AF4-81705554908F}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {BE244BB9-CE9D-44BE-9AF4-81705554908F}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {BE244BB9-CE9D-44BE-9AF4-81705554908F}.Release|Any CPU.Build.0 = Release|Any CPU
57 | EndGlobalSection
58 | GlobalSection(SolutionProperties) = preSolution
59 | HideSolutionNode = FALSE
60 | EndGlobalSection
61 | GlobalSection(ExtensibilityGlobals) = postSolution
62 | SolutionGuid = {88F06C70-1467-4254-BB07-2FA2A5C828FF}
63 | EndGlobalSection
64 | EndGlobal
65 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ConfigurationProperty.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Windows.Input;
5 | using GalaSoft.MvvmLight;
6 | using GalaSoft.MvvmLight.Command;
7 |
8 | namespace WgServerforWindows.Models
9 | {
10 | public class ConfigurationProperty : ObservableObject, IDataErrorInfo
11 | {
12 | public ConfigurationProperty(ConfigurationBase configuration, ConfigurationProperty dependentProperty = null) =>
13 | _configuration = configuration;
14 |
15 | #region Public properties
16 |
17 | ///
18 | /// Allows ordering Properties. Default value is: int.MaxValue / 2
19 | /// Any value less than that will order properties before those with no custom value.
20 | /// Any value higher than that will order properties after those with no custom value.
21 | ///
22 | public int Index { get; set; } = int.MaxValue / 2;
23 |
24 | public string Name { get; set; }
25 |
26 | // Maps to the name in the .conf file
27 | public string PersistentPropertyName { get; set; }
28 |
29 | public string Description { get; set; }
30 |
31 | public string Value
32 | {
33 | get => _value ?? GetValueFunc?.Invoke();
34 | set => Set(nameof(Value), ref _value, value);
35 | }
36 | private string _value;
37 |
38 | ///
39 | /// Allows defining a method for evaluating the value, rather than setting it
40 | ///
41 | public Func GetValueFunc { get; set; }
42 |
43 | public string DefaultValue
44 | {
45 | // Without explicitly using init-only setter, only apply the default value if the value is empty.
46 | // This allows for real empty values to be saved later.
47 | set
48 | {
49 | if (string.IsNullOrEmpty(Value))
50 | {
51 | Value = value;
52 | }
53 | }
54 | }
55 |
56 | public bool IsReadOnly { get; set; }
57 |
58 | public bool IsEnabled { get; set; } = true;
59 |
60 | public bool IsHidden { get; set; }
61 |
62 | ///
63 | /// Specifies whether the property's value is calculated on the fly based on in-memory data.
64 | ///
65 | ///
66 | /// Properties for whom is true will NOT be read from or persisted to data confs (but may be persisted to wg confs).
67 | ///
68 | public bool IsCalculated { get; set; } = false;
69 |
70 | public ConfigurationPropertyAction Action { get; set; }
71 |
72 | public ConfigurationPropertyValidation Validation { get; set; }
73 |
74 | public HashSet TargetTypes { get; } = new HashSet();
75 |
76 | ///
77 | /// An action to be invoked after the configuration has been loaded
78 | ///
79 | public Action OnLoadAction { get; set; }
80 |
81 | #region Commands
82 |
83 | public ICommand ExecuteActionCommand => _executeActionCommand ??= new RelayCommand(() =>
84 | {
85 | Action.Action?.Invoke(_configuration, this);
86 | });
87 | private RelayCommand _executeActionCommand;
88 |
89 | #endregion
90 |
91 | #endregion
92 |
93 | #region Private fields
94 |
95 | private readonly ConfigurationBase _configuration;
96 |
97 | #endregion
98 |
99 | #region IDataErrorInfo members
100 |
101 | public string Error => throw new NotImplementedException();
102 |
103 | public string this[string columnName]
104 | {
105 | get
106 | {
107 | string result = default;
108 |
109 | if (columnName == nameof(Value))
110 | {
111 | result = Validation?.Validate?.Invoke(this);
112 | }
113 |
114 | return result;
115 | }
116 | }
117 |
118 | #endregion
119 |
120 | #region Overrides
121 |
122 | // Override ToString for debugging
123 | public override string ToString() => $"{PersistentPropertyName}: '{Value}'";
124 |
125 | #endregion
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/PrivateNetworkPrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Input;
3 | using Microsoft.WindowsAPICodePack.Net;
4 | using WgServerforWindows.Properties;
5 |
6 | namespace WgServerforWindows.Models
7 | {
8 | public class PrivateNetworkPrerequisite : PrerequisiteItem
9 | {
10 | #region PrerequisiteItem members
11 |
12 | public PrivateNetworkPrerequisite() : this(new PrivateNetworkTaskSubCommand()) { }
13 |
14 | public PrivateNetworkPrerequisite(PrivateNetworkTaskSubCommand privateNetworkTaskSubCommand) : base
15 | (
16 | title: Resources.PrivateNetworkTitle,
17 | successMessage: Resources.PrivateNetworkSuccess,
18 | errorMessage: Resources.PrivateNetworkError,
19 | resolveText: Resources.PrivateNetworkResolve,
20 | configureText: Resources.PrivateNetworkConfigure
21 | )
22 | {
23 | _privateNetworkTaskSubCommand = privateNetworkTaskSubCommand;
24 | SubCommands.Add(_privateNetworkTaskSubCommand);
25 | }
26 |
27 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
28 | {
29 | bool result = false;
30 |
31 | // Check whether the Tunnel service is installed. This will inform whether we should wait a long time to find the network or not
32 | var tun = new TunnelServicePrerequisite().Fulfilled;
33 | TimeSpan timeout = TimeSpan.FromSeconds(tun ? 10 : 0);
34 |
35 | if (ServerConfigurationPrerequisite.GetNetwork(timeout: timeout) is { } network)
36 | {
37 | // Special case: computer is on a domain, so Authenticated is sufficient and shouldn't be changed
38 | if (network.Category == NetworkCategory.Authenticated)
39 | {
40 | SuccessMessage = Resources.WireGuardNetworkOnDomain;
41 | _isInformational = true;
42 | }
43 | else
44 | {
45 | SuccessMessage = Resources.PrivateNetworkSuccess;
46 | _isInformational = false;
47 | }
48 |
49 | RaisePropertyChanged(nameof(CanConfigure));
50 | RaisePropertyChanged(nameof(CanResolve));
51 |
52 | // Normal case: We want the network to be private
53 | result = network.Category == NetworkCategory.Private;
54 | }
55 |
56 | return result;
57 | });
58 | private BooleanTimeCachedProperty _fulfilled;
59 |
60 | public override void Resolve()
61 | {
62 | WaitCursor.SetOverrideCursor(Cursors.Wait);
63 |
64 | if (ServerConfigurationPrerequisite.GetNetwork() is { } network)
65 | {
66 | network.Category = NetworkCategory.Private;
67 | }
68 |
69 | _privateNetworkTaskSubCommand.Resolve();
70 |
71 | Refresh();
72 |
73 | WaitCursor.SetOverrideCursor(null);
74 | }
75 |
76 | public override void Configure()
77 | {
78 | WaitCursor.SetOverrideCursor(Cursors.Wait);
79 |
80 |
81 | if (ServerConfigurationPrerequisite.GetNetwork() is { } network)
82 | {
83 | try
84 | {
85 | network.Category = NetworkCategory.Public;
86 | }
87 | catch (UnauthorizedAccessException)
88 | {
89 | // If it failed, maybe we're on a domain?
90 | if (network.Category == NetworkCategory.Authenticated)
91 | {
92 | // Just keep going. Refresh() will raise Fulfilled, which will check the category agian
93 | }
94 | else // Failed for some other reason. Let it fail.
95 | {
96 | throw;
97 | }
98 | }
99 | }
100 |
101 | _privateNetworkTaskSubCommand.Configure();
102 |
103 | Refresh();
104 |
105 | WaitCursor.SetOverrideCursor(null);
106 | }
107 |
108 | public override BooleanTimeCachedProperty IsInformational => _isInformationalProperty ??= new BooleanTimeCachedProperty(TimeSpan.Zero, () => _isInformational);
109 | private BooleanTimeCachedProperty _isInformationalProperty;
110 | private bool _isInformational;
111 |
112 | #endregion
113 |
114 | #region Private fields
115 |
116 | private readonly PrivateNetworkTaskSubCommand _privateNetworkTaskSubCommand;
117 |
118 | #endregion
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/SelectionWindow.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
41 |
42 |
53 |
54 |
57 |
58 |
59 |
60 |
70 |
71 |
79 |
80 |
88 |
89 |
102 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/WgServerforWindowsCli/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | text/microsoft-resx
91 |
92 |
93 | 1.3
94 |
95 |
96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
97 |
98 |
99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
100 |
101 |
102 | Error: ws4w.exe must be run as Administrator.
103 |
104 |
105 | Command executed successfully.
106 |
107 |
108 | Command did not complete successfully.
109 |
110 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/ServerStatusPrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 | using System.Windows.Threading;
6 | using SharpConfig;
7 | using WgAPI;
8 | using WgAPI.Commands;
9 | using WgServerforWindows.Cli.Options;
10 | using WgServerforWindows.Controls;
11 | using WgServerforWindows.Properties;
12 | using System.Collections.Generic;
13 | using System.Linq;
14 |
15 | namespace WgServerforWindows.Models
16 | {
17 | public class ServerStatusPrerequisite : PrerequisiteItem
18 | {
19 | #region PrerequisiteItem members
20 |
21 | public ServerStatusPrerequisite() : base
22 | (
23 | title: Resources.ServerStatusTitle,
24 | successMessage: Resources.ServerStatusSuccessMessage,
25 | errorMessage: string.Empty,
26 | resolveText: string.Empty,
27 | configureText: Resources.ServerStatusConfigureText
28 | )
29 | {
30 | _updateTimer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)};
31 | _updateTimer.Tick += (_, __) =>
32 | {
33 | if (UpdateLive)
34 | {
35 | RaisePropertyChanged(nameof(ServerStatus));
36 | }
37 | };
38 | }
39 |
40 | public override BooleanTimeCachedProperty Fulfilled { get; } = new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () => true);
41 |
42 | public override void Resolve()
43 | {
44 | throw new NotImplementedException();
45 | }
46 |
47 | public override void Configure()
48 | {
49 | CliWrap.Cli.Wrap(Path.Combine(AppContext.BaseDirectory, "WgServerforWindows.exe"))
50 | .WithArguments(typeof(StatusCommand).GetVerb())
51 | .ExecuteAsync();
52 | }
53 |
54 | public override BooleanTimeCachedProperty IsInformational { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => true);
55 |
56 | #endregion
57 |
58 | #region Public methods
59 |
60 | public void Show()
61 | {
62 | _updateTimer.IsEnabled = true;
63 | new ServerStatusWindow { DataContext = this }.ShowDialog();
64 | _updateTimer.IsEnabled = false;
65 | }
66 |
67 | #endregion
68 |
69 | #region Public properties
70 |
71 | public string ServerStatus
72 | {
73 | get
74 | {
75 | Dictionary clients = new();
76 | try
77 | {
78 | // First, load all of the clients, so we can associate peer IDs.
79 | ClientConfigurationList clientConfigurations = new ClientConfigurationList();
80 | List clientConfigurationsFromFile = new List();
81 | foreach (string clientConfigurationFile in Directory.GetFiles(ClientConfigurationsPrerequisite.ClientDataDirectory, "*.conf"))
82 | {
83 | clientConfigurationsFromFile.Add(new ClientConfiguration(clientConfigurations).Load(Configuration.LoadFromFile(clientConfigurationFile)));
84 | }
85 |
86 | clients = clientConfigurationsFromFile.ToDictionary(c => c.PublicKeyProperty.Value, c => c.Name);
87 | }
88 | catch
89 | {
90 | // Ignore if there's any problem with this
91 | }
92 |
93 | // Get the output of the status command
94 | string statusOutput = new WireGuardExe().ExecuteCommand(new ShowCommand(GlobalAppSettings.Instance.TunnelServiceName));
95 |
96 | // Iterate through the output and correlate peer IDs to names
97 | StringBuilder result = new StringBuilder();
98 | foreach (string line in statusOutput.Split(Environment.NewLine))
99 | {
100 | Match match = _peerRegex.Match(line);
101 | if (match.Success && clients.ContainsKey(match.Groups[1].Value))
102 | {
103 | result.AppendLine(_peerRegex.Replace(line, $"peer: {clients[match.Groups[1].Value]} ({match.Groups[1].Value})"));
104 | }
105 | else
106 | {
107 | result.AppendLine(line);
108 | }
109 | }
110 |
111 | return result.ToString();
112 | }
113 | }
114 |
115 | public bool UpdateLive
116 | {
117 | get => _updateLive;
118 | set => Set(nameof(UpdateLive), ref _updateLive, value);
119 | }
120 | private bool _updateLive = true;
121 |
122 | #endregion
123 |
124 | #region Private fields
125 |
126 | private readonly DispatcherTimer _updateTimer;
127 | private readonly Regex _peerRegex = new Regex(@"peer:\s([^\s]+)", RegexOptions.Compiled);
128 |
129 | #endregion
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/WgAPI/WireGuardExe.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using CliWrap;
7 | using CliWrap.Buffered;
8 |
9 | namespace WgAPI
10 | {
11 | ///
12 | /// Represents the Windows wireguard.exe application that is downloaded and installed from https://www.wireguard.com/install/
13 | ///
14 | public class WireGuardExe
15 | {
16 | #region Constructor
17 |
18 | public WireGuardExe()
19 | {
20 | }
21 |
22 | #endregion
23 |
24 | #region Private methods
25 |
26 | private string GetPath(WhichExe whichExe)
27 | {
28 | string result = default;
29 |
30 | string fileName = whichExe switch
31 | {
32 | WhichExe.WireGuardExe => "wireguard.exe",
33 | WhichExe.WGExe => "wg.exe",
34 | // This case should never be hit, since custom exes provide their own name via Args
35 | _ => default
36 | };
37 |
38 | // Must use EnvironmentVariableTarget.Machine so that we always get the latest variables, even if they change after our process starts.
39 | if (Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.Machine) is { } pathEnv)
40 | {
41 | foreach (string path in pathEnv.Split(';'))
42 | {
43 | string wireGuardExePath = Path.Combine(path, fileName);
44 | if (File.Exists(wireGuardExePath))
45 | {
46 | result = wireGuardExePath;
47 | break;
48 | }
49 | }
50 | }
51 |
52 | return result;
53 | }
54 |
55 | #endregion
56 |
57 | #region Public properties
58 |
59 | public bool Exists => string.IsNullOrEmpty(GetPath(WhichExe.WireGuardExe)) == false;
60 |
61 | #endregion
62 |
63 | #region Public methods
64 |
65 | public string ExecuteCommand(WireGuardCommand command)
66 | {
67 | return ExecuteCommand(command, out _);
68 | }
69 |
70 | public string ExecuteCommand(WireGuardCommand command, out int exitCode)
71 | {
72 | string result = default;
73 | exitCode = 1;
74 |
75 | switch (command.WhichExe)
76 | {
77 | case WhichExe.WireGuardExe:
78 | case WhichExe.WGExe:
79 | var cmd = command.StandardInput | Cli.Wrap(GetPath(command.WhichExe)).WithArguments(a => a
80 | .Add(command.Switch)
81 | .Add(command.Args)).WithValidation(CommandResultValidation.None);
82 |
83 | // For some reason, awaiting this can hang, so add a little retry and invoke the command in a task
84 | for (int i = 0; i < 10; ++i)
85 | {
86 | int taskExitCode = 1;
87 | Task.Run(() =>
88 | {
89 | var bufferedResultTask = cmd.ExecuteBufferedAsync();
90 | if (bufferedResultTask.Task.Wait(TimeSpan.FromSeconds(10)))
91 | {
92 | var bufferedResult = bufferedResultTask.GetAwaiter().GetResult();
93 | result = bufferedResult.StandardOutput.Trim();
94 | taskExitCode = bufferedResult.ExitCode;
95 |
96 | if (taskExitCode != 0)
97 | {
98 | result += bufferedResult.StandardError.Trim();
99 | }
100 | }
101 | }).GetAwaiter().GetResult();
102 |
103 | exitCode = taskExitCode;
104 | }
105 |
106 | break;
107 | case WhichExe.Custom:
108 | Process process = Process.Start(new ProcessStartInfo
109 | {
110 | FileName = command.Args[0],
111 | Arguments = string.Join(' ', command.Args.Skip(1)),
112 | CreateNoWindow = true,
113 | RedirectStandardOutput = true
114 | });
115 | process?.WaitForExit((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
116 | result = process?.StandardOutput.ReadToEnd();
117 | exitCode = process?.ExitCode ?? 1;
118 | break;
119 | case WhichExe.CustomInteractive:
120 | process = Process.Start(new ProcessStartInfo
121 | {
122 | FileName = command.Args[0],
123 | Arguments = string.Join(' ', command.Args.Skip(1)),
124 | });
125 | process?.WaitForExit();
126 | exitCode = process?.ExitCode ?? 1;
127 | break;
128 | }
129 |
130 | return result;
131 | }
132 |
133 | #endregion
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/WgServerforWindows/Controls/ClientConfigurationEditorWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/TunnelServicePrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Net.NetworkInformation;
5 | using System.Windows.Input;
6 | using SharpConfig;
7 | using WgAPI;
8 | using WgAPI.Commands;
9 | using WgServerforWindows.Controls;
10 | using WgServerforWindows.Properties;
11 |
12 | namespace WgServerforWindows.Models
13 | {
14 | public class TunnelServicePrerequisite : PrerequisiteItem
15 | {
16 | public TunnelServicePrerequisite() : this(new TunnelServiceNameSubCommand())
17 | {
18 | }
19 |
20 | public TunnelServicePrerequisite(TunnelServiceNameSubCommand tunnelServiceNameSubCommand) : base
21 | (
22 | title: Resources.TunnelService,
23 | successMessage: Resources.TunnelServiceInstalled,
24 | errorMessage: Resources.TunnelServiceNotInstalled,
25 | resolveText: Resources.InstallTunnelService,
26 | configureText: Resources.UninstallTunnelService
27 | )
28 | {
29 | SubCommands.Add(tunnelServiceNameSubCommand);
30 | }
31 |
32 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
33 | {
34 | return NetworkInterface.GetAllNetworkInterfaces()
35 | .Any(nic => nic.Name == GlobalAppSettings.Instance.TunnelServiceName);
36 | });
37 | private BooleanTimeCachedProperty _fulfilled;
38 |
39 | public override async void Resolve()
40 | {
41 | try
42 | {
43 | // Load the server config and check the listen port
44 | ServerConfiguration serverConfiguration = new ServerConfiguration().Load(Configuration.LoadFromFile(ServerConfigurationPrerequisite.ServerDataPath));
45 | string listenPort = serverConfiguration.ListenPortProperty.Value;
46 |
47 | if (int.TryParse(listenPort, out int listenPortInt))
48 | {
49 | IPEndPoint[] tcpEndPoints = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
50 | IPEndPoint[] udpEndPoints = IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
51 |
52 | bool anyTcpListener = tcpEndPoints.Any(endpoint => endpoint.Port == listenPortInt);
53 | bool anyUdpListener = udpEndPoints.Any(endpoint => endpoint.Port == listenPortInt);
54 |
55 | if (anyUdpListener)
56 | {
57 | // Give the user strong warning about UDP listener
58 | bool canceled = false;
59 | UnhandledErrorWindow portWarningDialog = new UnhandledErrorWindow();
60 | portWarningDialog.DataContext = new UnhandledErrorWindowModel
61 | {
62 | Title = Resources.PotentialPortConflict,
63 | Text = string.Format(Resources.UDPPortConflictMessage, listenPort),
64 | SecondaryButtonText = Resources.Cancel,
65 | SecondaryButtonAction = () =>
66 | {
67 | canceled = true;
68 | portWarningDialog.Close();
69 | }
70 | };
71 | portWarningDialog.ShowDialog();
72 |
73 | if (canceled)
74 | {
75 | return;
76 | }
77 | }
78 | else if (anyTcpListener)
79 | {
80 | // Give the user less strong warning about TCP listener
81 | bool canceled = false;
82 | UnhandledErrorWindow portWarningDialog = new UnhandledErrorWindow();
83 | portWarningDialog.DataContext = new UnhandledErrorWindowModel
84 | {
85 | Title = Resources.PotentialPortConflict,
86 | Text = string.Format(Resources.TCPPortConflictMessage, listenPort),
87 | SecondaryButtonText = Resources.Cancel,
88 | SecondaryButtonAction = () =>
89 | {
90 | canceled = true;
91 | portWarningDialog.Close();
92 | }
93 | };
94 | portWarningDialog.ShowDialog();
95 |
96 | if (canceled)
97 | {
98 | return;
99 | }
100 | }
101 | }
102 | }
103 | catch
104 | {
105 | // If we can't verify the listen port, it's ok.
106 | }
107 |
108 | WaitCursor.SetOverrideCursor(Cursors.Wait);
109 |
110 | using (TemporaryFile temporaryFile = new(ServerConfigurationPrerequisite.ServerWGPath, ServerConfigurationPrerequisite.ServerWGPathWithCustomTunnelName))
111 | {
112 | new WireGuardExe().ExecuteCommand(new InstallTunnelServiceCommand(temporaryFile.NewFilePath));
113 | }
114 |
115 | await WaitForFulfilled();
116 |
117 | WaitCursor.SetOverrideCursor(null);
118 | }
119 |
120 | public override async void Configure()
121 | {
122 | WaitCursor.SetOverrideCursor(Cursors.Wait);
123 |
124 | new WireGuardExe().ExecuteCommand(new UninstallTunnelServiceCommand(GlobalAppSettings.Instance.TunnelServiceName));
125 | await WaitForFulfilled(false);
126 |
127 | WaitCursor.SetOverrideCursor(null);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/WgServerforWindows.Tests/ServerConfigurationEndpointPropertyTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using WgServerforWindows.Models;
3 | using WgServerforWindows.Properties;
4 | using Xunit;
5 |
6 | namespace WgServerforWindows.Tests
7 | {
8 | public class ServerConfigurationEndpointPropertyTests
9 | {
10 | ///
11 | /// Test Fixture
12 | ///
13 | public ServerConfigurationEndpointPropertyTests()
14 | {
15 | _endpointConfigurationProperty = (_serverConfiguration = new ServerConfiguration()).EndpointProperty;
16 | }
17 |
18 | [Theory]
19 | [InlineData(null, null)]
20 | [InlineData(null, "1")]
21 | [InlineData("1", null)]
22 | [InlineData("", "")]
23 | [InlineData(":", "")]
24 | [InlineData(":1", "1")]
25 | [InlineData(":a", "a")]
26 | [InlineData("1:", "")]
27 | private void ShouldFailGeneralValidation(string value, string port)
28 | {
29 | // Have to set the port to match; do this before setting the value.
30 | _serverConfiguration.ListenPortProperty.Value = port;
31 |
32 | _endpointConfigurationProperty.Value = value;
33 | string result = _endpointConfigurationProperty.Validation.Validate(_endpointConfigurationProperty);
34 |
35 | // A non-empty validation result indicates failure, which is what we want for this test.
36 | result.Should().NotBeNullOrEmpty();
37 |
38 | // None of our failures should be due to a port mismatch.
39 | result.Should().NotBeEquivalentTo(Resources.EndpointPortMismatch);
40 | }
41 |
42 | [Theory]
43 | [InlineData("1.1.1.1:51820", "51821")]
44 | [InlineData("hostname.com:51820", "51821")]
45 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "51821")]
46 | private void ShouldFailPortValidation(string value, string port)
47 | {
48 | _serverConfiguration.ListenPortProperty.Value = port;
49 |
50 | _endpointConfigurationProperty.Value = value;
51 | string result = _endpointConfigurationProperty.Validation.Validate(_endpointConfigurationProperty);
52 |
53 | // This time we're looking for a specific validation error
54 | result.Should().BeEquivalentTo(Resources.EndpointPortMismatch);
55 | }
56 |
57 | [Theory]
58 | [InlineData("1.1.1.1:51820", "51820")]
59 | [InlineData("hostname.com:51820", "51820")]
60 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "51820")]
61 | private void ShouldPassValidation(string value, string port)
62 | {
63 | // Have to set the port to match; do this before setting the value.
64 | _serverConfiguration.ListenPortProperty.Value = port;
65 |
66 | _endpointConfigurationProperty.Value = value;
67 | string result = _endpointConfigurationProperty.Validation.Validate(_endpointConfigurationProperty);
68 |
69 | // An empty validation result indicates successful validation, which is what we want for this test.
70 | result.Should().BeNullOrEmpty();
71 | }
72 |
73 | [Theory]
74 | [InlineData(null, "")]
75 | [InlineData("", "")]
76 | [InlineData(":", "")]
77 | [InlineData(":1", "")]
78 | [InlineData("1:", "1")]
79 | [InlineData("1:2", "1")]
80 | [InlineData("1.1.1.1:51820", "1.1.1.1")]
81 | [InlineData("hostname.com:51820", "hostname.com")]
82 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]")]
83 | private void HostGetterShouldWork(string value, string expectedHost)
84 | {
85 | _endpointConfigurationProperty.Value = value;
86 | _endpointConfigurationProperty.Host.Should().BeEquivalentTo(expectedHost);
87 | }
88 |
89 | [Theory]
90 | [InlineData(null, "")]
91 | [InlineData("", "")]
92 | [InlineData(":", "")]
93 | [InlineData(":1", "1")]
94 | [InlineData("1:", "")]
95 | [InlineData("1:2", "2")]
96 | [InlineData("1.1.1.1:51820", "51820")]
97 | [InlineData("hostname.com:51820", "51820")]
98 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "51820")]
99 | private void PortGetterShouldWork(string value, string expectedPort)
100 | {
101 | _endpointConfigurationProperty.Value = value;
102 | _endpointConfigurationProperty.Port.Should().BeEquivalentTo(expectedPort);
103 | }
104 |
105 | [Theory]
106 | [InlineData(null, "1", "1:")]
107 | [InlineData("", "1", "1:")]
108 | [InlineData(":", "1", "1:")]
109 | [InlineData(":1", "2", "2:1")]
110 | [InlineData("1:", "2", "2:")]
111 | [InlineData("1:2", "3", "3:2")]
112 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "3", "3:51820")]
113 | private void HostModificationShouldSucceed(string oldValue, string newHost, string newValue)
114 | {
115 | _endpointConfigurationProperty.Value = oldValue;
116 | _endpointConfigurationProperty.Host = newHost;
117 | _endpointConfigurationProperty.Value.Should().BeEquivalentTo(newValue);
118 | }
119 |
120 | [Theory]
121 | [InlineData(null, "1", ":1")]
122 | [InlineData("", "1", ":1")]
123 | [InlineData(":", "1", ":1")]
124 | [InlineData(":1", "2", ":2")]
125 | [InlineData("1:", "2", "1:2")]
126 | [InlineData("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820", "3", "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:3")]
127 | private void PortModificationShouldSucceed(string oldValue, string newPort, string newValue)
128 | {
129 | _endpointConfigurationProperty.Value = oldValue;
130 | _endpointConfigurationProperty.Port = newPort;
131 | _endpointConfigurationProperty.Value.Should().BeEquivalentTo(newValue);
132 | }
133 |
134 | private readonly ServerConfiguration _serverConfiguration;
135 | private readonly EndpointConfigurationProperty _endpointConfigurationProperty;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Installer/WS4WSetupScript.iss:
--------------------------------------------------------------------------------
1 | #define MyAppNameOld "WireGuard Server For Windows"
2 | #define MyAppName "Wg Server for Windows"
3 | #define MyAppVersion "2.1.4"
4 | #define MyAppPublisher "Micah Morrison"
5 | #define MyAppURL "https://github.com/micahmo/WgServerforWindows"
6 | #define MyAppExeName "WgServerforWindows.exe"
7 | #define CliName "ws4w.exe"
8 | #define NetRuntimeMinorVersion "11"
9 | #define NetRuntimeVersion "8.0." + NetRuntimeMinorVersion
10 | #define NetRuntime "windowsdesktop-runtime-" + NetRuntimeVersion + "-win-x64.exe"
11 | #define UniversalCrtKb "KB3118401"
12 | #define BuildConfig "Release"
13 | ;#define BuildConfig "Debug"
14 |
15 | ; This is relative to SourceDir
16 | #define RepoRoot "..\..\..\.."
17 |
18 | [Setup]
19 | ;PrivilegesRequired=admin
20 | AppId={{7EE6B381-7799-4674-B83C-5B07C71A5851}
21 | AppName={#MyAppName}
22 | AppVersion={#MyAppVersion}
23 | AppVerName={#MyAppName} {#MyAppVersion}
24 | AppPublisher={#MyAppPublisher}
25 | AppPublisherURL={#MyAppURL}
26 | AppSupportURL={#MyAppURL}
27 | AppUpdatesURL={#MyAppURL}
28 | DefaultDirName={autopf}\WS4W
29 | DefaultGroupName=WS4W
30 | AllowNoIcons=yes
31 | ; This is relative to the .iss file location
32 | SourceDir=..\WgServerforWindows\bin\{#BuildConfig}\net80-windows\
33 | ; These are relative to SourceDir (see RepoRoot)
34 | OutputDir={#RepoRoot}\Installer
35 | SetupIconFile={#RepoRoot}\WgServerforWindows\Images\logo.ico
36 | ; This is an install-time path, so it must refer to something on the installed machine, like the main exe
37 | UninstallDisplayIcon={app}\WgServerforWindows.exe
38 | OutputBaseFilename=WS4WSetup-{#MyAppVersion}
39 | Compression=lzma
40 | SolidCompression=yes
41 | WizardStyle=modern
42 | ; .NET Desktop Runtime install can trigger this, but it doesn't actually require a restart
43 | RestartIfNeededByRun=no
44 |
45 | [CustomMessages]
46 | UCrtError={#MyAppName} requires the Universal C Runtime. Please perform all outstanding Windows Updates or search for and install {#UniversalCrtKb} before installing WS4W.
47 |
48 | [Code]
49 | function NetRuntimeNotInstalled: Boolean;
50 | var
51 | InstalledRuntimes: TArrayOfString;
52 | I: Integer;
53 | MinorVersion: String;
54 | MinorVersionInt: Longint;
55 | begin
56 | Result := True;
57 |
58 | // Check if ANY .NET Desktop Runtime exists
59 | if RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.NETCore.App') then
60 | begin
61 | // Get all of the installed runtimes
62 | if RegGetValueNames(HKEY_LOCAL_MACHINE, 'SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.NETCore.App', InstalledRuntimes) then
63 | begin
64 | for I := 0 to GetArrayLength(InstalledRuntimes)-1 do
65 | begin
66 | // See if the runtime starts with 8.0.
67 | if WildcardMatch(InstalledRuntimes[I], '8.0.*') then
68 | begin
69 | // Get just the minor version and convert it to an int
70 | MinorVersion := InstalledRuntimes[I];
71 | Delete(MinorVersion, 1, 4);
72 | MinorVersionInt := StrToIntDef(MinorVersion, 0);
73 |
74 | // Check if it's at least the version we want
75 | if MinorVersionInt >= {#NetRuntimeMinorVersion} then
76 | begin
77 | // Finally, this system has a new enough version installed
78 | Result := False;
79 | Break;
80 | end
81 | end
82 | end
83 | end
84 | end
85 | end;
86 |
87 | // More info: https://docs.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170
88 | function UniversalCrtInstalled: Boolean;
89 | begin
90 | Result := FileExists(ExpandConstant('{sys}') + '\ucrtbase.dll');
91 | end;
92 |
93 | // This is a buit-in function that's called during initialization.
94 | // We'll use it to determine whether we can proceed with the install on this system.
95 | function InitializeSetup(): Boolean;
96 | begin
97 | if not UniversalCrtInstalled then
98 | begin
99 | MsgBox(ExpandConstant('{cm:UCrtError}'), mbCriticalError, MB_OK);
100 | Result := False;
101 | end
102 | else
103 | Result := True
104 | end;
105 |
106 | [Languages]
107 | Name: "english"; MessagesFile: "compiler:Default.isl"
108 |
109 | [Tasks]
110 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
111 | Name: "setpath"; Description: "Add '{app}' to the PATH variable for CLI access."; GroupDescription: "{cm:AdditionalIcons}"
112 |
113 | [InstallDelete]
114 | ; Manually clean up files which use the old name
115 | Type: files; Name: "{app}\WireGuardAPI.dll"
116 | Type: files; Name: "{app}\WireGuardAPI.pdb"
117 | Type: files; Name: "{app}\WireGuardServerForWindows.Cli.Options.dll"
118 | Type: files; Name: "{app}\WireGuardServerForWindows.Cli.Options.pdb"
119 | Type: files; Name: "{app}\WireGuardServerForWindows.deps.json"
120 | Type: files; Name: "{app}\WireGuardServerForWindows.dll"
121 | Type: files; Name: "{app}\WireGuardServerForWindows.exe"
122 | Type: files; Name: "{app}\WireGuardServerForWindows.pdb"
123 | Type: files; Name: "{app}\WireGuardServerForWindows.runtimeconfig.dev.json"
124 | Type: files; Name: "{app}\WireGuardServerForWindows.runtimeconfig.json"
125 | ; Delete old shortcuts
126 | Type: files; Name: "{group}\{#MyAppNameOld}.lnk"
127 | Type: files; Name: "{autodesktop}\{#MyAppNameOld}.lnk"
128 |
129 | [Files]
130 | ; These are relative to SourceDir
131 | Source: "*"; DestDir: "{app}"; Excludes: "de,es"; Flags: recursesubdirs;
132 | Source: "..\..\..\..\Installer\{#NetRuntime}"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: NetRuntimeNotInstalled
133 |
134 | [Icons]
135 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
136 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
137 |
138 | [Run]
139 | ; .NET Desktop Runtime
140 | Filename: "{tmp}\{#NetRuntime}"; Flags: runascurrentuser; StatusMsg: "Installing .NET Desktop Runtime..."; Check: NetRuntimeNotInstalled
141 |
142 | ; CLI in Path
143 | Filename: "{app}\{#CliName}"; Parameters: "setpath"; Flags: runhidden nowait skipifsilent runascurrentuser; Tasks: setpath
144 |
145 | ; runascurrentuser is needed to launch as admin
146 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser
147 |
148 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | text/microsoft-resx
91 |
92 |
93 | 1.3
94 |
95 |
96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
97 |
98 |
99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
100 |
101 |
102 | Attempts to Disable Internet Sharing and then Enable Internet Sharing.
103 |
104 |
105 | Attempts to call New-NetIPAddress on the WireGuard network interface.
106 |
107 |
108 | Adds the directory containing the currently executing ws4w.exe to the system's PATH environment variable.
109 |
110 |
111 | Specify the network to share with WireGuard. Useful if internet sharing is not already enabled, or to differentiate when multiple networks are already shared.
112 |
113 |
114 | The path to the WireGuard server configuration file.
115 |
116 |
117 | Unable to load the system's PATH environment variable.
118 |
119 |
120 | Unable to detect the current working directory.
121 |
122 |
123 | Successfully added '{0}' to the PATH.
124 |
125 |
126 | Found that '{0}' was already in the PATH.
127 |
128 |
129 | Sets the category of the WireGuard network interface to Private.
130 |
131 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/PersistentInternetSharingPrerequisite.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Management;
6 | using System.Windows;
7 | using System.Windows.Input;
8 | using Microsoft.Win32;
9 | using Microsoft.Win32.TaskScheduler;
10 | using WgServerforWindows.Cli.Options;
11 | using WgServerforWindows.Properties;
12 |
13 | namespace WgServerforWindows.Models
14 | {
15 | public class PersistentInternetSharingPrerequisite : PrerequisiteItem
16 | {
17 | #region PrerequisiteItem members
18 |
19 | public PersistentInternetSharingPrerequisite() : base
20 | (
21 | title: Resources.PersistentInternetSharingTitle,
22 | successMessage: Resources.PersistentInternetSharingSucecss,
23 | errorMessage: Resources.PersistentInternetSharingError,
24 | resolveText: Resources.PersistentInternetSharingResolve,
25 | configureText: Resources.PersistentInternetSharingDisable
26 | )
27 | {
28 | }
29 |
30 | public override BooleanTimeCachedProperty Fulfilled => _fulfilled ??= new BooleanTimeCachedProperty(TimeSpan.FromSeconds(1), () =>
31 | {
32 | bool result = false;
33 |
34 | try
35 | {
36 | // First, check whether the service is set to start automatically
37 | if (GetICSService() is { } service)
38 | {
39 | bool isAutomatic = service.Properties["StartMode"].Value as string == "Automatic" ||
40 | service.Properties["StartMode"].Value as string == "Auto";
41 |
42 | if (isAutomatic)
43 | {
44 | // Now check whether the special registry entry exists
45 | // If good, result is true
46 | if (GetRegistryKeyValue() is { } value && value == 1)
47 | {
48 | // Finally, verify that the task exists and that all of the parameters are correct.
49 | result = TaskService.Instance.FindTask(RestartInternetSharingTaskUniqueName) is { Enabled: true } task
50 | && task.Definition.Triggers.FirstOrDefault() is BootTrigger
51 | && task.Definition.Actions.FirstOrDefault() is ExecAction action
52 | && action.Path == Path.Combine(AppContext.BaseDirectory, "ws4w.exe")
53 | && action.Arguments == typeof(RestartInternetSharingCommand).GetVerb();
54 | }
55 | }
56 | }
57 | }
58 | catch
59 | {
60 | // If there's any error getting the ICS Service, then it's clearly not resolved, so return false.
61 | }
62 |
63 | return result;
64 | });
65 | private BooleanTimeCachedProperty _fulfilled;
66 |
67 | public override void Resolve()
68 | {
69 | WaitCursor.SetOverrideCursor(Cursors.Wait);
70 |
71 | if (GetICSService() is { } service)
72 | {
73 | var parameters = service.GetMethodParameters("ChangeStartMode");
74 | parameters["StartMode"] = "Automatic";
75 | service.InvokeMethod("ChangeStartMode", parameters, null);
76 | }
77 |
78 | SetRegistryKeyValue(1);
79 |
80 | // Create/update a Scheduled Task that disables/enables internet sharing on boot.
81 | TaskDefinition td = TaskService.Instance.NewTask();
82 | td.Actions.Add(new ExecAction(Path.Combine(AppContext.BaseDirectory, "ws4w.exe"), typeof(RestartInternetSharingCommand).GetVerb()));
83 | td.Triggers.Add(new BootTrigger { Delay = GlobalAppSettings.Instance.BootTaskDelay });
84 | TaskService.Instance.RootFolder.RegisterTaskDefinition(RestartInternetSharingTaskUniqueName, td, TaskCreation.CreateOrUpdate, "SYSTEM", null, TaskLogonType.ServiceAccount);
85 |
86 | Refresh();
87 |
88 | WaitCursor.SetOverrideCursor(null);
89 | }
90 |
91 | public override void Configure()
92 | {
93 | WaitCursor.SetOverrideCursor(Cursors.Wait);
94 |
95 | if (GetICSService() is { } service)
96 | {
97 | var parameters = service.GetMethodParameters("ChangeStartMode");
98 | parameters["StartMode"] = "Manual";
99 | service.InvokeMethod("ChangeStartMode", parameters, null);
100 | }
101 |
102 | SetRegistryKeyValue(0);
103 |
104 | if (TaskService.Instance.FindTask(RestartInternetSharingTaskUniqueName) is { } task)
105 | {
106 | task.Enabled = false;
107 | }
108 |
109 | Refresh();
110 |
111 | WaitCursor.SetOverrideCursor(null);
112 | }
113 |
114 | public override string Category => Resources.InternetConnectionSharing;
115 |
116 | #endregion
117 |
118 | #region Private methods
119 |
120 | private ManagementObject GetICSService()
121 | {
122 | ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("root\\cimv2", "select * from win32_service where name = 'SharedAccess'");
123 | return managementObjectSearcher.Get().OfType().FirstOrDefault();
124 | }
125 |
126 | private int? GetRegistryKeyValue()
127 | {
128 | RegistryKey sharedAccessKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\SharedAccess");
129 | return sharedAccessKey?.GetValue("EnableRebootPersistConnection") as int?;
130 | }
131 |
132 | private void SetRegistryKeyValue(int value)
133 | {
134 | RegistryKey sharedAccessKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\SharedAccess", writable: true)
135 | ?? Registry.LocalMachine.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\SharedAccess", writable: true);
136 |
137 | if (sharedAccessKey is null)
138 | {
139 | throw new Exception("There was an error setting the SharedAccess registry key.");
140 | }
141 |
142 | sharedAccessKey.SetValue("EnableRebootPersistConnection", value);
143 | }
144 |
145 | #endregion
146 |
147 | #region Private fields
148 |
149 | private readonly string RestartInternetSharingTaskUniqueName = "WS4W Restart Internet Sharing (b17f2530-acc7-42d6-ad05-ab57b923356f)";
150 |
151 | #endregion
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/WgServerforWindows/Models/PrerequisiteItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using GalaSoft.MvvmLight;
10 | using GalaSoft.MvvmLight.Command;
11 | using Xceed.Wpf.AvalonDock.Controls;
12 |
13 | namespace WgServerforWindows.Models
14 | {
15 | public abstract class PrerequisiteItem : ObservableObject
16 | {
17 | #region Constructor
18 |
19 | protected PrerequisiteItem(string title, string successMessage, string errorMessage, string resolveText, string configureText)
20 | {
21 | Title = title;
22 | SuccessMessage = successMessage;
23 | ErrorMessage = errorMessage;
24 | ResolveText = resolveText;
25 | ConfigureText = configureText;
26 | Commands = new PrerequisiteItemCommands(this);
27 |
28 | Children.CollectionChanged += (_, __) =>
29 | {
30 | RaisePropertyChanged(nameof(HasChildren));
31 | };
32 |
33 | SubCommands.CollectionChanged += (_, __) =>
34 | {
35 | RaisePropertyChanged(nameof(HasSubCommands));
36 | };
37 |
38 | Refresh();
39 | }
40 |
41 | #endregion
42 |
43 | #region Overrides
44 |
45 | ///
46 | public override void RaisePropertyChanged([CallerMemberName] string propertyName = null)
47 | {
48 | if (propertyName == nameof(CanResolve))
49 | {
50 | Commands.ResolveCommand.RaiseCanExecuteChanged();
51 | }
52 |
53 | if (propertyName == nameof(CanConfigure))
54 | {
55 |
56 | Commands.ConfigureCommand.RaiseCanExecuteChanged();
57 | }
58 |
59 | base.RaisePropertyChanged(propertyName);
60 | }
61 |
62 | #endregion
63 |
64 | #region Public properties
65 |
66 | public PrerequisiteItemCommands Commands { get; }
67 |
68 | public string Title
69 | {
70 | get => _title;
71 | set => Set(nameof(Title), ref _title, value);
72 | }
73 | private string _title;
74 |
75 | public virtual BooleanTimeCachedProperty Fulfilled { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => true);
76 |
77 | public virtual string SuccessMessage
78 | {
79 | get => _successMessage;
80 | set => Set(nameof(SuccessMessage), ref _successMessage, value);
81 | }
82 | private string _successMessage;
83 |
84 | public string ErrorMessage
85 | {
86 | get => _errorMessage;
87 | set => Set(nameof(ErrorMessage), ref _errorMessage, value);
88 | }
89 | private string _errorMessage;
90 |
91 | public string ResolveText
92 | {
93 | get => _resolveText;
94 | set => Set(nameof(ResolveText), ref _resolveText, value);
95 | }
96 | private string _resolveText;
97 |
98 | public string ConfigureText
99 | {
100 | get => _configureText;
101 | set => Set(nameof(ConfigureText), ref _configureText, value);
102 | }
103 | private string _configureText;
104 |
105 | public virtual BooleanTimeCachedProperty IsInformational { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => false);
106 |
107 | public virtual BooleanTimeCachedProperty HasIcon { get; } = new BooleanTimeCachedProperty(TimeSpan.Zero, () => true);
108 |
109 | public bool CanResolve => CanResolveFunc?.Invoke() ?? true;
110 |
111 | public Func CanResolveFunc { get; set; }
112 |
113 | public bool CanConfigure => CanConfigureFunc?.Invoke() ?? true;
114 |
115 | public Func CanConfigureFunc { get; set; }
116 |
117 | public ObservableCollection Children { get; } = new ObservableCollection();
118 |
119 | public bool HasChildren => Children.Any();
120 |
121 | public ObservableCollection SubCommands { get; } = new ObservableCollection();
122 |
123 | public bool HasSubCommands => SubCommands.Any();
124 |
125 | public virtual string Category { get; }
126 |
127 | public IEnumerable> ChildrenByCategory => Children.GroupBy(c => c.Category);
128 |
129 | public int SelectedChildIndex { get; set; }
130 |
131 | public Control Control => Application.Current?.Windows.OfType().FirstOrDefault()?.FindVisualChildren().FirstOrDefault(b => b.DataContext == this);
132 |
133 | #endregion
134 |
135 | #region Public methods
136 |
137 | public virtual void Resolve() { }
138 |
139 | public virtual void Configure() { }
140 |
141 | public void Refresh()
142 | {
143 | RaisePropertyChanged(nameof(Fulfilled));
144 | RaisePropertyChanged(nameof(IsInformational));
145 | }
146 |
147 | public async Task WaitForFulfilled()
148 | {
149 | await WaitForFulfilled(true);
150 | }
151 |
152 | public async Task WaitForFulfilled(bool value)
153 | {
154 | while (Fulfilled != value)
155 | {
156 | await Task.Delay((int) TimeSpan.FromSeconds(1).TotalMilliseconds);
157 | }
158 |
159 | Refresh();
160 | }
161 |
162 | public virtual void Update() { }
163 |
164 | #endregion
165 | }
166 |
167 | public class PrerequisiteItemCommands : ObservableObject
168 | {
169 | public PrerequisiteItemCommands(PrerequisiteItem prerequisiteItem)
170 | {
171 | PrerequisiteItem = prerequisiteItem;
172 | }
173 |
174 | private PrerequisiteItem PrerequisiteItem { get; }
175 |
176 | #region ICommands
177 |
178 | // The canExecute parameter is only needed for xctk:SplitButton, which uses ICommand.CanExecute to determine enabled status (instead of IsEnabled) when a Command is bound.
179 | // https://github.com/xceedsoftware/wpftoolkit/issues/1466
180 | public RelayCommand ResolveCommand => _resolveCommand ??= new RelayCommand(PrerequisiteItem.Resolve, PrerequisiteItem.HasSubCommands ? PrerequisiteItem.CanResolveFunc : null);
181 | private RelayCommand _resolveCommand;
182 |
183 | // The canExecute parameter is only needed for xctk:SplitButton, which uses ICommand.CanExecute to determine enabled status (instead of IsEnabled) when a Command is bound.
184 | // https://github.com/xceedsoftware/wpftoolkit/issues/1466
185 | public RelayCommand ConfigureCommand => _configureCommand ??= new RelayCommand(PrerequisiteItem.Configure, PrerequisiteItem.HasSubCommands ? PrerequisiteItem.CanConfigureFunc : null);
186 | private RelayCommand _configureCommand;
187 |
188 | #endregion
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/WgServerforWindows.Cli.Options/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace WgServerforWindows.Cli.Options.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WgServerforWindows.Cli.Options.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Successfully added '{0}' to the PATH..
65 | ///
66 | public static string AddedPwdToPath {
67 | get {
68 | return ResourceManager.GetString("AddedPwdToPath", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Unable to load the system's PATH environment variable..
74 | ///
75 | public static string CantLoadPath {
76 | get {
77 | return ResourceManager.GetString("CantLoadPath", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Unable to detect the current working directory..
83 | ///
84 | public static string CantLoadPwd {
85 | get {
86 | return ResourceManager.GetString("CantLoadPwd", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to Found that '{0}' was already in the PATH..
92 | ///
93 | public static string FoundPwdInPath {
94 | get {
95 | return ResourceManager.GetString("FoundPwdInPath", resourceCulture);
96 | }
97 | }
98 |
99 | ///
100 | /// Looks up a localized string similar to Sets the category of the WireGuard network interface to Private..
101 | ///
102 | public static string PrivateNetworkHelpText {
103 | get {
104 | return ResourceManager.GetString("PrivateNetworkHelpText", resourceCulture);
105 | }
106 | }
107 |
108 | ///
109 | /// Looks up a localized string similar to Attempts to Disable Internet Sharing and then Enable Internet Sharing..
110 | ///
111 | public static string RestartInternetSharingHelpText {
112 | get {
113 | return ResourceManager.GetString("RestartInternetSharingHelpText", resourceCulture);
114 | }
115 | }
116 |
117 | ///
118 | /// Looks up a localized string similar to Specify the network to share with WireGuard. Useful if internet sharing is not already enabled, or to differentiate when multiple networks are already shared..
119 | ///
120 | public static string RestartInternetSharingNetworkHelpText {
121 | get {
122 | return ResourceManager.GetString("RestartInternetSharingNetworkHelpText", resourceCulture);
123 | }
124 | }
125 |
126 | ///
127 | /// Looks up a localized string similar to The path to the WireGuard server configuration file..
128 | ///
129 | public static string SetNetIpAddressCommandServerDataPathHelpText {
130 | get {
131 | return ResourceManager.GetString("SetNetIpAddressCommandServerDataPathHelpText", resourceCulture);
132 | }
133 | }
134 |
135 | ///
136 | /// Looks up a localized string similar to Attempts to call New-NetIPAddress on the WireGuard network interface..
137 | ///
138 | public static string SetNetIpAddressHelpText {
139 | get {
140 | return ResourceManager.GetString("SetNetIpAddressHelpText", resourceCulture);
141 | }
142 | }
143 |
144 | ///
145 | /// Looks up a localized string similar to Adds the directory containing the currently executing ws4w.exe to the system's PATH environment variable..
146 | ///
147 | public static string SetPathHelpText {
148 | get {
149 | return ResourceManager.GetString("SetPathHelpText", resourceCulture);
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------