├── 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 | 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 " 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 |