mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-26 03:58:45 +08:00
Compare commits
172 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b59518364 | ||
|
|
ba72c1666c | ||
|
|
43735b1d04 | ||
|
|
002f6cb290 | ||
|
|
92c617d442 | ||
|
|
764b96f864 | ||
|
|
e30a8ad3f0 | ||
|
|
1e85969e3a | ||
|
|
1a47bba982 | ||
|
|
1cc5f05bf5 | ||
|
|
7e0277f85d | ||
|
|
ff3da2c7d5 | ||
|
|
c90152c35c | ||
|
|
95e20afefa | ||
|
|
4c7b3a9da5 | ||
|
|
14ec6b38e8 | ||
|
|
f10bcdee20 | ||
|
|
af105a62f1 | ||
|
|
5173eec41d | ||
|
|
797a8dff14 | ||
|
|
7dcc43824a | ||
|
|
9b04d70276 | ||
|
|
21407c0805 | ||
|
|
286eee370a | ||
|
|
bd4c9fea34 | ||
|
|
9ba21e9047 | ||
|
|
2e7ec5a354 | ||
|
|
d83be65e5c | ||
|
|
fec445d3a5 | ||
|
|
87fb45c115 | ||
|
|
779b541702 | ||
|
|
24d2a0b407 | ||
|
|
da10db53ee | ||
|
|
4487545445 | ||
|
|
afbec5fae0 | ||
|
|
6c20941430 | ||
|
|
8f79b313c5 | ||
|
|
295fcaae12 | ||
|
|
3ffac2684c | ||
|
|
26978fc09d | ||
|
|
151d82a640 | ||
|
|
12b4afac9a | ||
|
|
12f2942202 | ||
|
|
0b7fd5b5f5 | ||
|
|
fd41e27d47 | ||
|
|
8a3688c852 | ||
|
|
be7e5a6308 | ||
|
|
1a0de7cb2d | ||
|
|
7bc0ea29b1 | ||
|
|
4328d36a1d | ||
|
|
3a2576384b | ||
|
|
6d35121a85 | ||
|
|
b127968a6c | ||
|
|
27dd79c940 | ||
|
|
cb5d63224a | ||
|
|
2a55fbf1d4 | ||
|
|
44651c9d4c | ||
|
|
1222c87760 | ||
|
|
acfb035027 | ||
|
|
93f6d017e6 | ||
|
|
455d897b9f | ||
|
|
3d4d1136f0 | ||
|
|
598136f089 | ||
|
|
865bddfb04 | ||
|
|
3b2bc028cc | ||
|
|
71c746f801 | ||
|
|
079814751e | ||
|
|
1de9ce6ef5 | ||
|
|
01df0e295e | ||
|
|
2631926eda | ||
|
|
9ba3cf0727 | ||
|
|
71911bba7b | ||
|
|
e9f94cbe31 | ||
|
|
db1496d4a2 | ||
|
|
a8b12c9ba1 | ||
|
|
a2b408e537 | ||
|
|
a871624ab7 | ||
|
|
e40a800bc4 | ||
|
|
48cb1ce770 | ||
|
|
6733d758aa | ||
|
|
f78b9bf51c | ||
|
|
213ea6a4d3 | ||
|
|
ac3d7e6366 | ||
|
|
9fae2e066e | ||
|
|
cf66e84d31 | ||
|
|
f7326aff62 | ||
|
|
d4bca6b03d | ||
|
|
1d86015f9d | ||
|
|
5efcc653eb | ||
|
|
333588e101 | ||
|
|
4046681fc5 | ||
|
|
91c7df22b2 | ||
|
|
173b208eb1 | ||
|
|
50de37c3a4 | ||
|
|
380e91de07 | ||
|
|
ba97933f33 | ||
|
|
539b761199 | ||
|
|
aaff6e4548 | ||
|
|
a1e3b34580 | ||
|
|
276585e379 | ||
|
|
9242dc0faf | ||
|
|
14d08d8d62 | ||
|
|
421d8eb581 | ||
|
|
aed0006f1d | ||
|
|
86d986a633 | ||
|
|
5163aeb4d2 | ||
|
|
a103939c64 | ||
|
|
bcb8e05698 | ||
|
|
2e74313f07 | ||
|
|
c9791454ec | ||
|
|
b1c1696acb | ||
|
|
ab668ac677 | ||
|
|
1777c4840a | ||
|
|
90fcd672d8 | ||
|
|
4226396cbf | ||
|
|
3591ca0f4c | ||
|
|
09cbcccf30 | ||
|
|
4e36b5666e | ||
|
|
308253a325 | ||
|
|
30bb4016b4 | ||
|
|
e616ae16e1 | ||
|
|
469528a07a | ||
|
|
c7f831e9f9 | ||
|
|
dd9922be45 | ||
|
|
d0a468c22b | ||
|
|
ff5dbc88fe | ||
|
|
3a4ae9b098 | ||
|
|
2de24e534c | ||
|
|
59412a65e1 | ||
|
|
adedb6962f | ||
|
|
d3fa608ae9 | ||
|
|
3159903875 | ||
|
|
31c28c4917 | ||
|
|
2c13f7c3a7 | ||
|
|
725ee92147 | ||
|
|
14406f79a2 | ||
|
|
3e202c3a19 | ||
|
|
412c727e4c | ||
|
|
31e7b9e443 | ||
|
|
a18e292765 | ||
|
|
258ea87f90 | ||
|
|
fd9eabe1e6 | ||
|
|
a9dd06df45 | ||
|
|
dfc24b0cb2 | ||
|
|
63f5325f29 | ||
|
|
6f7949214b | ||
|
|
6cfec060a0 | ||
|
|
db733424ca | ||
|
|
49eab4d377 | ||
|
|
53e5396031 | ||
|
|
99d01130bf | ||
|
|
62ce6cd6e7 | ||
|
|
35c66b0e4a | ||
|
|
dafc05dc42 | ||
|
|
458a05ea5f | ||
|
|
38acc1e960 | ||
|
|
f83209a2b9 | ||
|
|
9d1546c8b7 | ||
|
|
dca2517cf0 | ||
|
|
ff54ca9015 | ||
|
|
76cb0a1e0b | ||
|
|
c683854678 | ||
|
|
e9d0a8aa69 | ||
|
|
30cb1de711 | ||
|
|
699b93e175 | ||
|
|
c7df6f3715 | ||
|
|
f1d1a19779 | ||
|
|
469ca48592 | ||
|
|
208ce663b2 | ||
|
|
843243c75e | ||
|
|
b7ed5a300d | ||
|
|
415a649a76 |
242
.github/copilot-instructions.md
vendored
Normal file
242
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
# GitHub Copilot Instructions for mRemoteNG
|
||||
|
||||
## Project Overview
|
||||
|
||||
mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager for Windows. It's a fork of mRemote that allows users to view and manage all their remote connections (RDP, VNC, SSH, Telnet, HTTP/HTTPS, rlogin, Raw Socket, PowerShell remoting, AnyDesk) in a simple yet powerful interface.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Language**: C# with latest language version
|
||||
- **Framework**: .NET 10.0 for Windows (net10.0-windows10.0.26100.0)
|
||||
- **UI Framework**: Windows Forms with WPF support
|
||||
- **Target Platforms**: x64 and ARM64
|
||||
- **Testing Framework**: NUnit with NSubstitute for mocking
|
||||
- **Build System**: MSBuild with .NET SDK-style projects
|
||||
|
||||
## Building the Project
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2026 (version 18.4.0 or later)
|
||||
- .NET 10.0 Desktop Runtime
|
||||
- Windows 10/11 or Windows Server 2022+
|
||||
|
||||
### Build Commands
|
||||
```powershell
|
||||
# Restore NuGet packages
|
||||
dotnet restore
|
||||
|
||||
# Build the solution
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# Or for ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=arm64
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
The project uses NUnit for testing. Test projects are in `mRemoteNGTests/` directory.
|
||||
|
||||
```powershell
|
||||
dotnet test mRemoteNGTests/mRemoteNGTests.csproj
|
||||
```
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Main Projects
|
||||
- **mRemoteNG**: Main application project
|
||||
- **mRemoteNGTests**: Unit and integration tests
|
||||
- **mRemoteNGSpecs**: Specification tests
|
||||
- **ObjectListView.NetCore**: Custom list view control
|
||||
- **ExternalConnectors**: External protocol connector implementations
|
||||
- **mRemoteNGDocumentation**: reStructuredText documentation
|
||||
|
||||
### Key Directories in mRemoteNG Project
|
||||
- `App/`: Application startup and initialization
|
||||
- `Connection/`: Connection models, protocols, and management
|
||||
- `Config/`: Configuration and serialization (XML, CSV)
|
||||
- `Container/`: Container/folder node implementations
|
||||
- `Credential/`: Credential storage and repositories
|
||||
- `Language/`: Localization resource files (.resx)
|
||||
- `Security/`: Authentication, encryption, and password management
|
||||
- `Tree/`: Connection tree UI and management
|
||||
- `UI/`: User interface components and forms
|
||||
- `Tools/`: Utility classes and helpers
|
||||
|
||||
## Code Style and Conventions
|
||||
|
||||
### EditorConfig Settings
|
||||
The project uses EditorConfig (`.editorconfig` in `mRemoteNG/` directory) with these key rules:
|
||||
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Line Endings**: CRLF (Windows-style)
|
||||
- **Charset**: UTF-8 with BOM for C# files
|
||||
- **New Lines**: Opening braces on new lines (Allman style)
|
||||
- **Using Directives**: Sort System directives first
|
||||
- **this. Qualifier**: Do not use `this.` prefix unless necessary
|
||||
|
||||
### Naming Conventions
|
||||
- **Classes/Interfaces**: PascalCase (e.g., `ConnectionInfo`, `IInheritable`)
|
||||
- **Methods**: PascalCase (e.g., `GetConnection`, `SaveToXml`)
|
||||
- **Properties**: PascalCase (e.g., `ConnectionName`, `Port`)
|
||||
- **Fields**: Use camelCase for private fields, consider `_` prefix for backing fields
|
||||
- **Constants**: PascalCase
|
||||
|
||||
### Code Patterns
|
||||
|
||||
#### Inheritance System
|
||||
mRemoteNG has a sophisticated property inheritance system for connection settings:
|
||||
|
||||
1. Properties can be inherited from parent containers/folders
|
||||
2. Each inheritable property has a corresponding `Inherit<PropertyName>` boolean in `ConnectionInfoInheritance` class
|
||||
3. Use `IInheritable` interface for objects that support inheritance
|
||||
4. Example: `ConnectionFrameColor` property has `InheritConnectionFrameColor` boolean
|
||||
|
||||
#### Serialization
|
||||
The project supports multiple serialization formats:
|
||||
|
||||
**XML Serialization** (`Config/Serializers/ConnectionSerializers/Xml/`):
|
||||
- Node-based serializers (e.g., `XmlConnectionNodeSerializer28.cs`)
|
||||
- Deserializers handle backward compatibility
|
||||
- Attributes for properties and inheritance flags
|
||||
- Always maintain backward compatibility - old files must still load
|
||||
|
||||
**CSV Serialization** (`Config/Serializers/ConnectionSerializers/Csv/`):
|
||||
- Export connections to CSV format
|
||||
- Include both value and inheritance columns
|
||||
- Maintain column order consistency
|
||||
|
||||
#### Localization
|
||||
All user-facing strings must be localized:
|
||||
|
||||
1. Add entries to `Language/Language.resx` (English base)
|
||||
2. Use `Language.ResourceName` to access strings in code
|
||||
3. Resource naming:
|
||||
- Properties: `PropertyName` (e.g., `ConnectionFrameColor`)
|
||||
- Descriptions: `PropertyDescription<PropertyName>`
|
||||
- Enum values: `<EnumName><Value>` (e.g., `FrameColorRed`)
|
||||
4. Provide clear, concise descriptions for PropertyGrid tooltips
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### Adding a New Connection Property
|
||||
|
||||
1. **Add enum** (if needed) in `Connection/` directory
|
||||
2. **Add property** to `AbstractConnectionRecord.cs` or `ConnectionInfo.cs`
|
||||
- Add `[Category("CategoryName")]` attribute for PropertyGrid grouping
|
||||
- Add `[Description("Language.PropertyDescription<Name>")]` for tooltip
|
||||
- Use appropriate TypeConverter if needed
|
||||
3. **Add inheritance support** in `ConnectionInfoInheritance.cs`
|
||||
4. **Update serializers**:
|
||||
- XML: Update latest `XmlConnectionNodeSerializer*.cs` and `XmlConnectionsDeserializer.cs`
|
||||
- CSV: Update `CsvConnectionsSerializerMremotengFormat.cs`
|
||||
5. **Add localization** in `Language/Language.resx`
|
||||
6. **Write tests** in `mRemoteNGTests/Connection/`
|
||||
7. **Update documentation** in `mRemoteNGDocumentation/` if user-facing
|
||||
|
||||
#### UI Controls
|
||||
- Prefer existing mRemoteNG patterns for UI controls
|
||||
- Connection panels use `InterfaceControl` base class
|
||||
- Custom painting: Override `OnPaint` or handle `Paint` event
|
||||
- Follow Windows Forms best practices
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### Test Structure
|
||||
- Use NUnit's `[Test]` attribute for test methods
|
||||
- Use `[SetUp]` and `[TearDown]` for test initialization/cleanup
|
||||
- Use `[TestCase]` for parameterized tests
|
||||
- Use NSubstitute for mocking: `Substitute.For<IInterface>()`
|
||||
|
||||
### Test Naming
|
||||
- Use descriptive names: `MethodName_Scenario_ExpectedBehavior`
|
||||
- Example: `ConnectionInfo_SetPassword_EncryptsValue`
|
||||
|
||||
### Test Organization
|
||||
Mirror the main project structure in `mRemoteNGTests/`:
|
||||
- `Connection/` for connection tests
|
||||
- `Security/` for security tests
|
||||
- `Config/` for configuration/serialization tests
|
||||
|
||||
## Important Notes and Pitfalls
|
||||
|
||||
### Backward Compatibility
|
||||
- **Critical**: Never break loading of old connection files
|
||||
- Always provide default values for new properties
|
||||
- Test that files without new attributes still load correctly
|
||||
|
||||
### Security
|
||||
- Sensitive data (passwords, credentials) must be encrypted
|
||||
- Use existing security providers in `Security/` namespace
|
||||
- Never log or expose credentials in plain text
|
||||
|
||||
### Performance
|
||||
- Connection tree can contain thousands of nodes
|
||||
- Optimize for large connection files
|
||||
- Avoid unnecessary UI refreshes
|
||||
- Use async/await for I/O operations
|
||||
|
||||
### Platform-Specific Code
|
||||
- The project targets both x64 and ARM64
|
||||
- Avoid platform-specific code unless absolutely necessary
|
||||
- Test on both platforms when possible
|
||||
|
||||
### External Dependencies
|
||||
- PuTTY for SSH/Telnet (bundled)
|
||||
- Terminal Service Client for RDP
|
||||
- Various protocol-specific libraries (see CREDITS.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Documentation
|
||||
- Located in `mRemoteNGDocumentation/`
|
||||
- Written in reStructuredText (.rst)
|
||||
- Follows ReadTheDocs format
|
||||
- Include screenshots and examples for new features
|
||||
|
||||
### Code Documentation
|
||||
- Use XML documentation comments for public APIs
|
||||
- Document complex algorithms and business logic
|
||||
- Keep implementation notes in `IMPLEMENTATION_NOTES.md` for significant features
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a Protocol
|
||||
1. Create protocol implementation in `Connection/Protocol/`
|
||||
2. Add protocol enum value to protocol types
|
||||
3. Implement connection logic
|
||||
4. Add UI controls if needed
|
||||
5. Update documentation
|
||||
|
||||
### Adding a Theme
|
||||
1. Add theme files to `Themes/` directory
|
||||
2. Update theme manager
|
||||
3. Add documentation in `mRemoteNGDocumentation/themes/`
|
||||
|
||||
### Updating Dependencies
|
||||
- Dependencies are centrally managed in `Directory.Packages.props`
|
||||
- Use `dotnet restore` after updating
|
||||
- Test thoroughly after dependency updates
|
||||
|
||||
## Version and Release
|
||||
|
||||
- Current development version: 1.78.2-dev
|
||||
- Version is set in `mRemoteNG.csproj`
|
||||
- Builds are automated via GitHub Actions (`.github/workflows/`)
|
||||
- Changelog maintained in `CHANGELOG.md`
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Project website: https://mremoteng.org
|
||||
- Documentation: https://mremoteng.readthedocs.io
|
||||
- GitHub Issues: Report bugs and feature requests
|
||||
- Community: Reddit r/mRemoteNG, Matrix chat
|
||||
|
||||
## Summary
|
||||
|
||||
When contributing to mRemoteNG:
|
||||
1. Follow the existing code style (EditorConfig)
|
||||
2. Maintain backward compatibility with old connection files
|
||||
3. Localize all user-facing strings
|
||||
4. Write tests for new functionality
|
||||
5. Update documentation for user-facing changes
|
||||
6. Test on both x64 and ARM64 if possible
|
||||
7. Keep security and performance in mind
|
||||
177
.github/workflows/Build_mR-NB.yml
vendored
177
.github/workflows/Build_mR-NB.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build_and_Release_mR-NB
|
||||
name: Build_and_Release_mR-NB_MultiDeploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -19,12 +19,28 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Framework-Dependent builds (requires .NET runtime on user machine)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: framework-dependent
|
||||
deploy_suffix: FD
|
||||
# Self-Contained builds (includes .NET runtime)
|
||||
- runner: windows-latest
|
||||
platform: x64
|
||||
arch: x64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
- runner: windows-11-arm
|
||||
platform: ARM64
|
||||
arch: arm64
|
||||
deployment: self-contained
|
||||
deploy_suffix: SC
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# Only run if:
|
||||
# - manual dispatch, OR
|
||||
@@ -36,13 +52,18 @@ jobs:
|
||||
steps:
|
||||
- name: (01) Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: (02) Setup MSBuild
|
||||
|
||||
- name: (02) Install .NET 10 SDK
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: (03) Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
vs-version: '17.14.12'
|
||||
vs-version: '18.0'
|
||||
|
||||
- name: (03) Install and run dotnet-t4 to transform T4 templates
|
||||
- name: (04) Install and run dotnet-t4 to transform T4 templates
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global dotnet-t4
|
||||
@@ -56,24 +77,32 @@ jobs:
|
||||
env:
|
||||
PLATFORM: '${{ matrix.platform }}'
|
||||
|
||||
- name: (04) Cache NuGet Packages
|
||||
- name: (05) Cache NuGet Packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-
|
||||
${{ runner.os }}-${{ matrix.arch }}-nuget-
|
||||
|
||||
- name: (05) Restore NuGet Packages
|
||||
- name: (06) Restore NuGet Packages
|
||||
shell: pwsh
|
||||
run: dotnet restore
|
||||
|
||||
- name: (06) Build Release
|
||||
- name: (07) Build Framework-Dependent Release
|
||||
if: matrix.deployment == 'framework-dependent'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
|
||||
|
||||
- name: (07) Release Information
|
||||
|
||||
- name: (08) Build Self-Contained Release
|
||||
if: matrix.deployment == 'self-contained'
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" /t:Publish /p:Configuration="Release Self-Contained" -p:Platform=${{ matrix.platform }} /verbosity:minimal /p:SelfContained=true /p:PublishDir="bin\${{ matrix.platform }}\Release"
|
||||
|
||||
- name: (09) Release Information
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -86,7 +115,7 @@ jobs:
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}.zip"
|
||||
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}-${{ matrix.deploy_suffix }}.zip"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
@@ -94,9 +123,9 @@ jobs:
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
$version = "${{ steps.version.outputs.version }}"
|
||||
echo "deployment=${{ matrix.deployment }}" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: (08) Extract Changelog Section
|
||||
- name: (10) Extract Changelog Section
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -127,30 +156,128 @@ jobs:
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
echo "log=$escaped"
|
||||
|
||||
- name: (09) Create Zip File
|
||||
- name: (11) Create Zip File
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sourceDir = "$Env:GITHUB_WORKSPACE\mRemoteNG\bin\${{ matrix.platform }}\Release"
|
||||
Compress-Archive -Path "$sourceDir\*" -DestinationPath ${{ steps.version.outputs.zipname }}
|
||||
echo "File: ${{ steps.version.outputs.zipname }}"
|
||||
|
||||
# Show file size
|
||||
$fileSize = (Get-Item ${{ steps.version.outputs.zipname }}).Length / 1MB
|
||||
echo "Size: $([math]::Round($fileSize, 2)) MB"
|
||||
|
||||
- name: (10) Create release
|
||||
id: create_release
|
||||
- name: (10) Upload artifacts for combination
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-${{ matrix.arch }}-${{ matrix.deploy_suffix }}
|
||||
path: ${{ steps.version.outputs.zipname }}
|
||||
retention-days: 1
|
||||
|
||||
Create-Combined-Release:
|
||||
needs: NB-Build-and-Release
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Extract version info
|
||||
id: version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
|
||||
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
|
||||
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
|
||||
$version = $matches['ver']
|
||||
$build = $matches['build']
|
||||
} else {
|
||||
throw "Could not extract version and build number"
|
||||
}
|
||||
$date = Get-Date -Format "yyyyMMdd"
|
||||
$tag = "$date-v$version-NB-($build)"
|
||||
$message = git log -1 --pretty=%B
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "build=$build" >> $env:GITHUB_OUTPUT
|
||||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
echo "message=$message" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Extract Changelog
|
||||
id: changelog
|
||||
shell: pwsh
|
||||
run: |
|
||||
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
|
||||
$lines = Get-Content $changelogPath
|
||||
|
||||
$startIndex = -1
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## \[') {
|
||||
$startIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($startIndex -eq -1) {
|
||||
throw "No version header found in CHANGELOG.md"
|
||||
}
|
||||
|
||||
$section = @()
|
||||
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '^## ') {
|
||||
break
|
||||
}
|
||||
$section += $lines[$i]
|
||||
}
|
||||
|
||||
$joined = $section -join "`n"
|
||||
echo "log<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $joined >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Collect all release files
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem -Path artifacts -Recurse -Filter "*.zip" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination .
|
||||
echo "Found: $($_.Name)"
|
||||
}
|
||||
|
||||
- name: Create combined release
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
|
||||
files: ${{ steps.version.outputs.zipname }}
|
||||
files: '*.zip'
|
||||
body: |
|
||||
Changes in this Release:
|
||||
## mRemoteNG ${{ steps.version.outputs.version }} NB Build ${{ steps.version.outputs.build }}
|
||||
|
||||
### 📦 Available Downloads
|
||||
|
||||
**Framework-Dependent (FD)** - Requires .NET 10 Runtime installed:
|
||||
- Smaller download size (~15-25 MB)
|
||||
- Requires .NET 10 Desktop Runtime on user machine
|
||||
- Files: `*-FD.zip`
|
||||
|
||||
**Self-Contained (SC)** - Portable, no installation required:
|
||||
- Larger download size (~80-150 MB)
|
||||
- Includes .NET 10 runtime - no installation needed
|
||||
- Files: `*-SC.zip`
|
||||
|
||||
Both versions are available for **x64** and **ARM64** architectures.
|
||||
|
||||
---
|
||||
|
||||
### 📝 Changes in this Release
|
||||
${{ steps.changelog.outputs.log }}
|
||||
|
||||
Last Commit Message:
|
||||
|
||||
### 💬 Last Commit Message
|
||||
${{ steps.version.outputs.message }}
|
||||
|
||||
draft: false
|
||||
|
||||
63
.github/workflows/filter-links.yml
vendored
Normal file
63
.github/workflows/filter-links.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Remove other GitHub repo links
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
filter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sanitize links
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const githubRepoRegex =
|
||||
/\[[^\]]*\]\((?!https?:\/\/github\.com\/user-attachments\/assets\/)[^\)]*https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*\)|https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*/gi;
|
||||
|
||||
// CASE 1: Comment
|
||||
if (context.payload.comment) {
|
||||
const comment = context.payload.comment;
|
||||
console.log("Processing comment:", comment.id);
|
||||
const matches = comment.body.match(githubRepoRegex);
|
||||
console.log("Matches:", matches);
|
||||
if (!matches) return;
|
||||
const sanitized = comment.body.replace(
|
||||
githubRepoRegex,
|
||||
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
|
||||
);
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: comment.id,
|
||||
body: sanitized
|
||||
});
|
||||
console.log("Comment updated");
|
||||
return;
|
||||
}
|
||||
// CASE 2: Issue body
|
||||
const issue = context.payload.issue;
|
||||
if (issue) {
|
||||
console.log("Processing issue:", issue.number);
|
||||
const matches = issue.body?.match(githubRepoRegex);
|
||||
console.log("Matches:", matches);
|
||||
if (!matches) return;
|
||||
const sanitized = issue.body.replace(
|
||||
githubRepoRegex,
|
||||
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
|
||||
);
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: sanitized
|
||||
});
|
||||
console.log("Issue body updated");
|
||||
}
|
||||
167
DEPLOYMENT_OPTIONS.md
Normal file
167
DEPLOYMENT_OPTIONS.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# mRemoteNG Deployment Options
|
||||
|
||||
This document explains the two deployment options for mRemoteNG and how to build each version.
|
||||
|
||||
## Deployment Types
|
||||
|
||||
### 1. Framework-Dependent (FD)
|
||||
**File suffix: `-FD.zip`**
|
||||
|
||||
- **Size**: ~15-25 MB
|
||||
- **Requirements**: User must have .NET 10 Desktop Runtime installed
|
||||
- **Startup**: Application checks for .NET runtime and prompts user to download if missing
|
||||
- **Use case**: Standard release for users comfortable installing prerequisites
|
||||
|
||||
### 2. Self-Contained (SC)
|
||||
**File suffix: `-SC.zip`**
|
||||
|
||||
- **Size**: ~80-150 MB
|
||||
- **Requirements**: None - includes .NET 10 runtime
|
||||
- **Startup**: No runtime checks performed (runtime is bundled)
|
||||
- **Use case**: Portable version for users who want zero installation/configuration
|
||||
|
||||
## Building Locally
|
||||
|
||||
### Framework-Dependent Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
|
||||
|
||||
# ARM64
|
||||
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64
|
||||
```
|
||||
|
||||
### Self-Contained Build
|
||||
|
||||
```powershell
|
||||
# x64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-x64 `
|
||||
--self-contained true `
|
||||
-p:Platform=x64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
|
||||
# ARM64
|
||||
dotnet publish mRemoteNG\mRemoteNG.csproj `
|
||||
--configuration Release `
|
||||
--runtime win-arm64 `
|
||||
--self-contained true `
|
||||
-p:Platform=ARM64 `
|
||||
-p:PublishSingleFile=false `
|
||||
-p:PublishReadyToRun=true `
|
||||
-p:DefineConstants="SELF_CONTAINED"
|
||||
```
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The new workflow file `Build_and_Release_mR-NB-MultiDeploy.yml` automatically builds all four variants:
|
||||
|
||||
1. **x64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip`
|
||||
2. **x64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip`
|
||||
3. **ARM64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip`
|
||||
4. **ARM64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip`
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
The workflow runs when:
|
||||
- You push to `v1.78.2-dev` branch with commit message containing "NB release"
|
||||
- You manually trigger via workflow_dispatch
|
||||
|
||||
### Release Output
|
||||
|
||||
All four zip files are uploaded to a single GitHub Release with clear descriptions:
|
||||
- Framework-Dependent versions are marked as requiring .NET 10 Runtime
|
||||
- Self-Contained versions are marked as portable/no installation needed
|
||||
|
||||
## Code Changes
|
||||
|
||||
### ProgramRoot.cs
|
||||
|
||||
The `MainAsync` method now uses conditional compilation:
|
||||
|
||||
```csharp
|
||||
#if !SELF_CONTAINED
|
||||
// Runtime check code only included in Framework-Dependent builds
|
||||
// Checks for .NET Runtime and Visual C++ Redistributable
|
||||
#endif
|
||||
```
|
||||
|
||||
When building with `-p:DefineConstants="SELF_CONTAINED"`, the runtime checks are completely excluded from the compiled binary.
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For Users
|
||||
|
||||
**Choose Framework-Dependent (FD) if:**
|
||||
- You don't mind installing .NET 10 Desktop Runtime once
|
||||
- You want smaller download size
|
||||
- You're using multiple .NET applications (runtime is shared)
|
||||
|
||||
**Choose Self-Contained (SC) if:**
|
||||
- You want zero installation/setup
|
||||
- You need portable deployment (USB drive, restricted environments)
|
||||
- You don't want to deal with prerequisites
|
||||
|
||||
### For Distribution
|
||||
|
||||
Consider offering both options:
|
||||
- Make Framework-Dependent the **default/recommended** option (smaller, faster updates)
|
||||
- Offer Self-Contained as **portable alternative** for special use cases
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Compilation Symbols
|
||||
|
||||
- Framework-Dependent builds: No special symbols
|
||||
- Self-Contained builds: `SELF_CONTAINED` symbol defined
|
||||
|
||||
### Runtime Identifiers
|
||||
|
||||
- x64: `win-x64`
|
||||
- ARM64: `win-arm64`
|
||||
|
||||
### Publish Options
|
||||
|
||||
Self-contained builds use these optimizations:
|
||||
- `PublishReadyToRun=true` - AOT compilation for faster startup
|
||||
- `IncludeNativeLibrariesForSelfExtract=true` - Bundle native dependencies
|
||||
- `PublishSingleFile=false` - Keep files separate for better compatibility with mRemoteNG's plugin system
|
||||
|
||||
## File Size Comparison
|
||||
|
||||
Typical build sizes:
|
||||
|
||||
| Version | Framework-Dependent | Self-Contained |
|
||||
|---------|---------------------|----------------|
|
||||
| x64 | ~18 MB | ~95 MB |
|
||||
| ARM64 | ~18 MB | ~95 MB |
|
||||
|
||||
*Note: Self-contained includes entire .NET 10 runtime (~80 MB overhead)*
|
||||
|
||||
## Testing
|
||||
|
||||
### Framework-Dependent Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should prompt to download .NET 10 Runtime
|
||||
4. Install runtime and verify app launches
|
||||
|
||||
### Self-Contained Build
|
||||
1. Uninstall .NET 10 Runtime (if installed)
|
||||
2. Run mRemoteNG.exe
|
||||
3. Should launch immediately without runtime check
|
||||
4. Verify full functionality
|
||||
|
||||
## Migration from Old Workflow
|
||||
|
||||
The original workflow `Build_and_Release_mR-NB.yml` is preserved. To migrate:
|
||||
|
||||
1. Rename or remove old workflow: `Build_and_Release_mR-NB.yml`
|
||||
2. Rename new workflow: `Build_and_Release_mR-NB-MultiDeploy.yml` → `Build_and_Release_mR-NB.yml`
|
||||
3. Commit and push with "NB release" in message
|
||||
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1507</NoWarn>
|
||||
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.6" />
|
||||
<PackageVersion Include="AWSSDK.EC2" Version="4.0.65" />
|
||||
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.15" />
|
||||
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76.1" />
|
||||
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||
<PackageVersion Include="Castle.Core" Version="5.2.1" />
|
||||
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
|
||||
<PackageVersion Include="ConsoleControlAPI" Version="1.3.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="Cucumber.Messages" Version="31.1.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
|
||||
<PackageVersion Include="Cucumber.Messages" Version="32.0.1" />
|
||||
<PackageVersion Include="DockPanelSuite" Version="3.1.1" />
|
||||
<PackageVersion Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
|
||||
<PackageVersion Include="envdte" Version="17.14.40260" />
|
||||
<PackageVersion Include="Gherkin" Version="37.0.1" />
|
||||
<PackageVersion Include="Google.Protobuf" Version="3.33.2" />
|
||||
<PackageVersion Include="Gherkin" Version="38.0.0" />
|
||||
<PackageVersion Include="Google.Protobuf" Version="3.33.5" />
|
||||
<PackageVersion Include="LiteDB" Version="5.0.21" />
|
||||
<PackageVersion Include="log4net" Version="3.2.0" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageVersion Include="log4net" Version="3.3.0" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
|
||||
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageVersion Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
|
||||
<PackageVersion Include="MySql.Data" Version="9.5.0" />
|
||||
<PackageVersion Include="MySql.Data" Version="9.6.0" />
|
||||
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="NUnit" Version="4.4.0" />
|
||||
<PackageVersion Include="NUnit.Console" Version="3.21.1" />
|
||||
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.21.1" />
|
||||
<PackageVersion Include="NUnit" Version="4.5.0" />
|
||||
<PackageVersion Include="NUnit.Console" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.22.0" />
|
||||
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
|
||||
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="6.0.1" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
<PackageVersion Include="OpenCover" Version="4.7.1221" />
|
||||
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
|
||||
<PackageVersion Include="ReportGenerator" Version="5.5.1" />
|
||||
@@ -58,18 +58,19 @@
|
||||
<PackageVersion Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
|
||||
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
|
||||
<PackageVersion Include="System.Buffers" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Console" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.1" />
|
||||
<PackageVersion Include="System.DirectoryServices" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Data.Odbc" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />
|
||||
<PackageVersion Include="System.DirectoryServices" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
<PackageVersion Include="System.IO.Pipelines" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.1" />
|
||||
<PackageVersion Include="System.IO.Pipelines" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Memory" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
|
||||
@@ -77,7 +78,7 @@
|
||||
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Resources.ResourceManager" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Runtime" Version="4.3.1" />
|
||||
@@ -87,19 +88,19 @@
|
||||
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.OpenSsl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Security.Permissions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
|
||||
<PackageVersion Include="System.Windows.Extensions" Version="10.0.1" />
|
||||
<PackageVersion Include="System.ValueTuple" Version="4.6.2" />
|
||||
<PackageVersion Include="System.Windows.Extensions" Version="10.0.3" />
|
||||
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
|
||||
<PackageVersion Include="VaultSharp" Version="1.17.5.1" />
|
||||
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
|
||||
<PackageVersion Include="ZstdSharp.Port" Version="0.8.7" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,18 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<Platforms>x64;arm64</Platforms>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Portable;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Self-Contained;Deploy to github</Configurations>
|
||||
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|arm64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<Deterministic>false</Deterministic>
|
||||
<RootNamespace>BrightIdeasSoftware</RootNamespace>
|
||||
<AssemblyName>ObjectListView</AssemblyName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<NoWarn>$(NoWarn);WFO1000</NoWarn>
|
||||
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
|
||||
</PropertyGroup>
|
||||
@@ -35,8 +34,4 @@
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Rendering\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1906,7 +1906,7 @@ namespace BrightIdeasSoftware
|
||||
[Category("ObjectListView"),
|
||||
Description("The image list from which group header will take their images"),
|
||||
DefaultValue(null)]
|
||||
public ImageList GroupImageList
|
||||
public new ImageList GroupImageList
|
||||
{
|
||||
get { return this.groupImageList; }
|
||||
set
|
||||
|
||||
@@ -645,7 +645,6 @@ namespace BrightIdeasSoftware
|
||||
/// Mess with the basic message pump of the tooltip
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
override protected void WndProc(ref Message msg) {
|
||||
//System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg));
|
||||
switch (msg.Msg) {
|
||||
@@ -697,5 +696,4 @@ namespace BrightIdeasSoftware
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -101,7 +101,7 @@ namespace BrightIdeasSoftware
|
||||
/// <para>
|
||||
/// Although it isn't documented, .NET virtual lists cannot have checkboxes. This class codes around this limitation,
|
||||
/// but you must use the functions provided by ObjectListView: CheckedObjects, CheckObject(), UncheckObject() and their friends.
|
||||
/// If you use the normal check box properties (CheckedItems or CheckedIndicies), they will throw an exception, since the
|
||||
/// If you use the normal check box properties (CheckedItems or CheckedIndicie), they will throw an exception, since the
|
||||
/// list is in virtual mode, and .NET "knows" it can't handle checkboxes in virtual mode.
|
||||
/// </para>
|
||||
/// <para>Due to the limits of the underlying Windows control, virtual lists do not trigger ItemCheck/ItemChecked events.
|
||||
@@ -155,7 +155,7 @@ namespace BrightIdeasSoftware
|
||||
/// <para>
|
||||
/// This property returns a simple collection. Changes made to the returned
|
||||
/// collection do NOT affect the list. This is different to the behaviour of
|
||||
/// CheckedIndicies collection.
|
||||
/// CheckedIndicie collection.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When getting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects.
|
||||
@@ -405,8 +405,6 @@ namespace BrightIdeasSoftware
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_virtualListSize")]
|
||||
private static extern ref int GetVirtualListSizeField(ListView listView);
|
||||
|
||||
static private FieldInfo virtualListSizeFieldInfo;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OLV accessing
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31912.275
|
||||
@@ -13,8 +13,8 @@ Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|arm64 = Debug|arm64
|
||||
Debug|x64 = Debug|x64
|
||||
Release Installer and Portable|arm64 = Release Installer and Portable|arm64
|
||||
Release Installer and Portable|x64 = Release Installer and Portable|x64
|
||||
Release Self-Contained|arm64 = Release Self-Contained|arm64
|
||||
Release Self-Contained|x64 = Release Self-Contained|x64
|
||||
Release|arm64 = Release|arm64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
@@ -23,10 +23,10 @@ Global
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.Build.0 = Debug|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|arm64.ActiveCfg = Release Portable|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|arm64.Build.0 = Release Portable|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.Build.0 = Release|arm64
|
||||
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.ActiveCfg = Release|x64
|
||||
@@ -35,10 +35,10 @@ Global
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|arm64.Build.0 = Debug|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.Build.0 = Debug|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|arm64.ActiveCfg = Release Portable|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|arm64.Build.0 = Release Portable|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.ActiveCfg = Release|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.Build.0 = Release|arm64
|
||||
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.ActiveCfg = Release|x64
|
||||
@@ -47,10 +47,10 @@ Global
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|arm64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|arm64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace mRemoteNG.App
|
||||
public static class ProgramRoot
|
||||
{
|
||||
private static Mutex? _mutex;
|
||||
private static FrmSplashScreenNew _frmSplashScreen = null;
|
||||
private static string customResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
|
||||
|
||||
private static System.Threading.Thread? _wpfSplashThread;
|
||||
@@ -41,6 +40,9 @@ namespace mRemoteNG.App
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||
|
||||
#if !SELF_CONTAINED
|
||||
// Runtime checks only needed for framework-dependent deployments
|
||||
// Self-contained builds include the runtime, so no check is needed
|
||||
string? installedVersion = DotNetRuntimeCheck.GetLatestDotNetRuntimeVersion();
|
||||
//installedVersion = ""; // Force check for testing purposes
|
||||
|
||||
@@ -106,6 +108,7 @@ namespace mRemoteNG.App
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
Lazy<bool> singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
|
||||
if (singleInstanceOption.Value)
|
||||
|
||||
@@ -121,7 +121,11 @@ namespace mRemoteNG.App
|
||||
private static void RunUpdateFile()
|
||||
{
|
||||
if (UpdatePending)
|
||||
{
|
||||
// Validate the update file path to prevent command injection
|
||||
Tools.PathValidator.ValidateExecutablePathOrThrow(_updateFilePath, nameof(_updateFilePath));
|
||||
Process.Start(new ProcessStartInfo(_updateFilePath) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using LiteDB;
|
||||
using mRemoteNG.Resources.Language;
|
||||
|
||||
namespace mRemoteNG.Config.DatabaseConnectors
|
||||
{
|
||||
//[SupportedOSPlatform("windows")]
|
||||
/// <summary>
|
||||
/// A helper class for testing database connectivity
|
||||
/// A helper class for testing database connectivity.
|
||||
/// </summary>
|
||||
///
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class DatabaseConnectionTester
|
||||
{
|
||||
public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Build the connection string based on the provided parameters
|
||||
string connectionString = $"Data Source={server};Initial Catalog={database};User ID={username};Password={password}";
|
||||
|
||||
// Attempt to open a connection to the database
|
||||
using (SqlConnection connection = new SqlConnection(connectionString))
|
||||
{
|
||||
await connection.OpenAsync();
|
||||
}
|
||||
|
||||
using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
|
||||
await dbConnector.ConnectAsync();
|
||||
return ConnectionTestResult.ConnectionSucceded;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle specific SQL exceptions
|
||||
switch (ex.Number)
|
||||
{
|
||||
case 4060: // Invalid Database
|
||||
return ConnectionTestResult.UnknownDatabase;
|
||||
case 18456: // Login Failed
|
||||
return ConnectionTestResult.CredentialsRejected;
|
||||
case -1: // Server not accessible
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
default:
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle any other exceptions
|
||||
string message = ex.Message;
|
||||
if (message.Contains("server was not found", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("network-related", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("instance-specific", StringComparison.OrdinalIgnoreCase))
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
if (message.Contains("Cannot open database", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("Unknown database", StringComparison.OrdinalIgnoreCase))
|
||||
return ConnectionTestResult.UnknownDatabase;
|
||||
if (message.Contains("Login failed", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("Access denied", StringComparison.OrdinalIgnoreCase))
|
||||
return ConnectionTestResult.CredentialsRejected;
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
}
|
||||
}
|
||||
//public class DatabaseConnectionTester
|
||||
//{
|
||||
//public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
|
||||
//{
|
||||
//using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
|
||||
//try
|
||||
//{
|
||||
// Validate architecture compatibility
|
||||
//if (!Environment.Is64BitProcess)
|
||||
//{
|
||||
// throw new PlatformNotSupportedException("The application must run in a 64-bit process to use this database connector.");
|
||||
// }
|
||||
|
||||
// Attempt to connect
|
||||
|
||||
//using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
//{
|
||||
// connection.Open();
|
||||
// Console.WriteLine("Connection successful!");
|
||||
//}
|
||||
//Console.WriteLine($"{RuntimeInformation.OSArchitecture}");
|
||||
//Console.WriteLine($"{RuntimeInformation.ProcessArchitecture}");
|
||||
//try
|
||||
//{
|
||||
// using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
// {
|
||||
// connection.Open();
|
||||
// Console.WriteLine("Connection successful!");
|
||||
// }
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// Console.WriteLine($"Connection failed: {ex.Message}");
|
||||
//}
|
||||
//}
|
||||
/*
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
Console.WriteLine($"Type initialization error: {ex.InnerException?.Message}");
|
||||
}
|
||||
|
||||
|
||||
//await dbConnector.ConnectAsync();
|
||||
return ConnectionTestResult.ConnectionSucceded;
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
// Log or handle architecture mismatch
|
||||
Console.WriteLine(string.Format(Language.ErrorPlatformNotSupported, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (DllNotFoundException ex)
|
||||
{
|
||||
// Handle missing native dependencies
|
||||
Console.WriteLine(string.Format(Language.ErrorMissingDependency, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (BadImageFormatException ex)
|
||||
{
|
||||
// Handle architecture mismatch in native libraries
|
||||
Console.WriteLine(string.Format(Language.ErrorArchitectureMismatch, ex.Message));
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (SqlException sqlException)
|
||||
{
|
||||
if (sqlException.Message.Contains("The server was not found"))
|
||||
return ConnectionTestResult.ServerNotAccessible;
|
||||
if (sqlException.Message.Contains("Cannot open database"))
|
||||
return ConnectionTestResult.UnknownDatabase;
|
||||
if (sqlException.Message.Contains("Login failed for user"))
|
||||
return ConnectionTestResult.CredentialsRejected;
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log unexpected errors
|
||||
Console.WriteLine($"Unexpected error: {ex.Message}");
|
||||
return ConnectionTestResult.UnknownError;
|
||||
}
|
||||
*/
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Security.SymmetricEncryption;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace mRemoteNG.Config.DatabaseConnectors
|
||||
@@ -22,14 +23,13 @@ namespace mRemoteNG.Config.DatabaseConnectors
|
||||
|
||||
public static IDatabaseConnector DatabaseConnector(string type, string server, string database, string username, string password)
|
||||
{
|
||||
switch (type)
|
||||
return type switch
|
||||
{
|
||||
case "mysql":
|
||||
return new MySqlDatabaseConnector(server, database, username, password);
|
||||
case "mssql":
|
||||
default:
|
||||
return new MSSqlDatabaseConnector(server, database, username, password);
|
||||
}
|
||||
"mysql" => new MySqlDatabaseConnector(server, database, username, password),
|
||||
"odbc" => throw new NotSupportedException("ODBC database connections are not supported for schema initialization. Please use a supported database backend."),
|
||||
"mssql" => new MSSqlDatabaseConnector(server, database, username, password),
|
||||
_ => new MSSqlDatabaseConnector(server, database, username, password)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
106
mRemoteNG/Config/DatabaseConnectors/OdbcDatabaseConnector.cs
Normal file
106
mRemoteNG/Config/DatabaseConnectors/OdbcDatabaseConnector.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.Odbc;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// ReSharper disable ArrangeAccessorOwnerBody
|
||||
|
||||
namespace mRemoteNG.Config.DatabaseConnectors
|
||||
{
|
||||
public class OdbcDatabaseConnector : IDatabaseConnector
|
||||
{
|
||||
private DbConnection _dbConnection { get; set; } = default(OdbcConnection);
|
||||
private string _dbConnectionString = "";
|
||||
private readonly string _connectionString;
|
||||
private readonly string _dbUsername;
|
||||
private readonly string _dbPassword;
|
||||
|
||||
public DbConnection DbConnection()
|
||||
{
|
||||
return _dbConnection;
|
||||
}
|
||||
|
||||
public DbCommand DbCommand(string dbCommand)
|
||||
{
|
||||
return new OdbcCommand(dbCommand, (OdbcConnection) _dbConnection);
|
||||
}
|
||||
|
||||
public bool IsConnected => (_dbConnection.State == ConnectionState.Open);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ODBC database connector.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">
|
||||
/// An ODBC connection string or DSN name. If a plain DSN name is provided (no '=' character),
|
||||
/// it is automatically wrapped as "DSN=<name>".
|
||||
/// </param>
|
||||
/// <param name="username">Optional user name appended to the connection string as UID.</param>
|
||||
/// <param name="password">Optional password appended to the connection string as PWD.</param>
|
||||
public OdbcDatabaseConnector(string connectionString, string username, string password)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
_dbUsername = username;
|
||||
_dbPassword = password;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
BuildConnectionString();
|
||||
_dbConnection = new OdbcConnection(_dbConnectionString);
|
||||
}
|
||||
|
||||
private void BuildConnectionString()
|
||||
{
|
||||
// If no '=' present in the provided string it is a plain DSN name.
|
||||
string baseString = _connectionString.Contains('=')
|
||||
? _connectionString
|
||||
: $"DSN={_connectionString}";
|
||||
|
||||
OdbcConnectionStringBuilder builder = new()
|
||||
{
|
||||
ConnectionString = baseString
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(_dbUsername))
|
||||
builder["UID"] = _dbUsername;
|
||||
|
||||
if (!string.IsNullOrEmpty(_dbPassword))
|
||||
builder["PWD"] = _dbPassword;
|
||||
|
||||
_dbConnectionString = builder.ConnectionString;
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
_dbConnection.Open();
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await _dbConnection.OpenAsync();
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
_dbConnection.Close();
|
||||
}
|
||||
|
||||
public void AssociateItemToThisConnector(DbCommand dbCommand)
|
||||
{
|
||||
dbCommand.Connection = (OdbcConnection) _dbConnection;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool itIsSafeToFreeManagedObjects)
|
||||
{
|
||||
if (!itIsSafeToFreeManagedObjects) return;
|
||||
_dbConnection.Close();
|
||||
_dbConnection.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@ namespace mRemoteNG.Config.Settings
|
||||
SetShowSystemTrayIcon();
|
||||
SetAutoSave();
|
||||
LoadExternalAppsFromXml();
|
||||
SetAlwaysShowPanelTabs();
|
||||
|
||||
if (Properties.App.Default.ResetToolbars)
|
||||
SetToolbarsDefault();
|
||||
@@ -68,12 +67,6 @@ namespace mRemoteNG.Config.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetAlwaysShowPanelTabs()
|
||||
{
|
||||
if (Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelTabs)
|
||||
FrmMain.Default.pnlDock.DocumentStyle = DocumentStyle.DockingWindow;
|
||||
}
|
||||
|
||||
|
||||
private void SetSupportedCulture()
|
||||
{
|
||||
|
||||
@@ -465,4 +465,4 @@ namespace mRemoteNG.Connection
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,17 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
|
||||
// Username field is optional and not used in the CLI (reserved for future use)
|
||||
// Password field is piped via stdin when --with-password flag is used
|
||||
string anydeskId = _connectionInfo.Hostname.Trim();
|
||||
string arguments = $"{anydeskId}";
|
||||
|
||||
// Validate AnyDesk ID to prevent command injection
|
||||
// AnyDesk IDs are numeric or alphanumeric with @ and - characters for aliases
|
||||
if (!IsValidAnydeskId(anydeskId))
|
||||
{
|
||||
Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
|
||||
"Invalid AnyDesk ID format. Only alphanumeric characters, @, -, _, and . are allowed.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
string arguments = anydeskId;
|
||||
|
||||
// Add --with-password flag if password is provided
|
||||
bool hasPassword = !string.IsNullOrEmpty(_connectionInfo.Password);
|
||||
@@ -242,27 +252,45 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidAnydeskId(string anydeskId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(anydeskId))
|
||||
return false;
|
||||
|
||||
// AnyDesk IDs can be:
|
||||
// - Pure numeric (e.g., 123456789)
|
||||
// - Alphanumeric with @ for aliases (e.g., alias@ad)
|
||||
// - May contain hyphens and underscores in aliases
|
||||
// Reject any characters that could be used for command injection
|
||||
foreach (char c in anydeskId)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(c) && c != '@' && c != '-' && c != '_' && c != '.')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool StartAnydeskWithPassword(string anydeskPath, string arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use PowerShell to pipe the password to AnyDesk
|
||||
// This is the recommended way according to AnyDesk documentation
|
||||
string escapedPassword = _connectionInfo.Password.Replace("'", "''");
|
||||
string powershellCommand = $"echo '{escapedPassword}' | & '{anydeskPath}' {arguments}";
|
||||
|
||||
// Start AnyDesk and pipe the password directly to stdin
|
||||
// This avoids command injection vulnerabilities from using PowerShell
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = $"-WindowStyle Hidden -Command \"{powershellCommand}\"",
|
||||
FileName = anydeskPath,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
CreateNoWindow = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
RedirectStandardInput = false
|
||||
RedirectStandardInput = true
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
@@ -270,8 +298,23 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
|
||||
_process.Exited += ProcessExited;
|
||||
_process.Start();
|
||||
|
||||
// Write the password to stdin and close the stream
|
||||
// AnyDesk expects the password on stdin when --with-password is used
|
||||
try
|
||||
{
|
||||
if (_process.StandardInput != null)
|
||||
{
|
||||
_process.StandardInput.WriteLine(_connectionInfo.Password);
|
||||
_process.StandardInput.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
|
||||
$"Failed to send password to AnyDesk: {ex.Message}", true);
|
||||
}
|
||||
|
||||
// Wait for the AnyDesk window to appear
|
||||
// Note: The window belongs to the AnyDesk process, not PowerShell
|
||||
if (!WaitForAnydeskWindow())
|
||||
{
|
||||
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
|
||||
|
||||
@@ -39,9 +39,9 @@ namespace mRemoteNG.Connection.Protocol
|
||||
|
||||
public IntPtr PuttyHandle { get; set; }
|
||||
|
||||
private Process PuttyProcess { get; set; }
|
||||
private Process? PuttyProcess { get; set; }
|
||||
|
||||
public static string PuttyPath { get; set; }
|
||||
public static string? PuttyPath { get; set; }
|
||||
|
||||
public bool Focused => NativeMethods.GetForegroundWindow() == PuttyHandle;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace mRemoteNG.Connection.Protocol
|
||||
|
||||
public bool isRunning()
|
||||
{
|
||||
return !PuttyProcess.HasExited;
|
||||
return PuttyProcess?.HasExited == false;
|
||||
}
|
||||
|
||||
public void CreatePipe(object oData)
|
||||
@@ -298,6 +298,9 @@ namespace mRemoteNG.Connection.Protocol
|
||||
while (PuttyHandle.ToInt32() == 0 &
|
||||
Environment.TickCount < startTicks + Properties.OptionsAdvancedPage.Default.MaxPuttyWaitTime * 1000)
|
||||
{
|
||||
if (PuttyProcess.HasExited)
|
||||
break;
|
||||
|
||||
if (_isPuttyNg)
|
||||
{
|
||||
PuttyHandle = NativeMethods.FindWindowEx(InterfaceControl.Handle, new IntPtr(0), null, null);
|
||||
@@ -305,12 +308,28 @@ namespace mRemoteNG.Connection.Protocol
|
||||
else
|
||||
{
|
||||
PuttyProcess.Refresh();
|
||||
PuttyHandle = PuttyProcess.MainWindowHandle;
|
||||
IntPtr candidateHandle = PuttyProcess.MainWindowHandle;
|
||||
|
||||
if (candidateHandle != IntPtr.Zero)
|
||||
{
|
||||
// Check the window class name to distinguish the actual PuTTY
|
||||
// terminal window ("PuTTY") from popup dialogs like the host key
|
||||
// verification alert (class "#32770"). Dialogs must remain as
|
||||
// top-level windows so the user can interact with them.
|
||||
StringBuilder className = new(256);
|
||||
NativeMethods.GetClassName(candidateHandle, className, className.Capacity);
|
||||
string cls = className.ToString();
|
||||
|
||||
if (cls.Equals("PuTTY", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
PuttyHandle = candidateHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PuttyHandle.ToInt32() == 0)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,27 +424,23 @@ namespace mRemoteNG.Connection.Protocol
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PuttyProcess.HasExited == false)
|
||||
if (PuttyProcess?.HasExited == false)
|
||||
{
|
||||
PuttyProcess.Kill();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
|
||||
Language.PuttyKillFailed + Environment.NewLine + ex.Message,
|
||||
true);
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyKillFailed + Environment.NewLine + ex.Message, true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PuttyProcess.Dispose();
|
||||
PuttyProcess?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
|
||||
Language.PuttyDisposeFailed + Environment.NewLine + ex.Message,
|
||||
true);
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyDisposeFailed + Environment.NewLine + ex.Message, true);
|
||||
}
|
||||
|
||||
base.Close();
|
||||
@@ -440,9 +455,7 @@ namespace mRemoteNG.Connection.Protocol
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
|
||||
Language.PuttyShowSettingsDialogFailed + Environment.NewLine +
|
||||
ex.Message, true);
|
||||
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyShowSettingsDialogFailed + Environment.NewLine + ex.Message, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -641,20 +641,27 @@ namespace mRemoteNG.Connection.Protocol.RDP
|
||||
_rdpClient.UserName = userName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
// Restricted Admin and Remote Credential Guard modes use the current user's Kerberos
|
||||
// credentials and do not forward explicit passwords to the remote host.
|
||||
// Skipping password assignment avoids potential NTLM fallback attempts that would
|
||||
// fail for accounts in the AD Protected Users security group.
|
||||
if (!connectionInfo.UseRestrictedAdmin && !connectionInfo.UseRCG)
|
||||
{
|
||||
if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
|
||||
if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
|
||||
{
|
||||
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey);
|
||||
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
|
||||
{
|
||||
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
|
||||
else
|
||||
{
|
||||
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(domain))
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace mRemoteNG.Connection
|
||||
#region IComponent
|
||||
|
||||
[Browsable(false)]
|
||||
public ISite Site
|
||||
public ISite? Site
|
||||
{
|
||||
get => new PropertyGridCommandSite(this);
|
||||
set => throw (new NotImplementedException());
|
||||
@@ -103,7 +103,7 @@ namespace mRemoteNG.Connection
|
||||
Disposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler Disposed;
|
||||
public event EventHandler? Disposed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
38
mRemoteNG/Language/Language.Designer.cs
generated
38
mRemoteNG/Language/Language.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace mRemoteNG.Resources.Language {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Language {
|
||||
@@ -177,6 +177,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to &Sessions.
|
||||
/// </summary>
|
||||
internal static string _Sessions {
|
||||
get {
|
||||
return ResourceManager.GetString("_Sessions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to &Stop.
|
||||
/// </summary>
|
||||
@@ -2909,6 +2918,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Jump to Session {0}.
|
||||
/// </summary>
|
||||
internal static string JumpToSession {
|
||||
get {
|
||||
return ResourceManager.GetString("JumpToSession", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to (Automatically Detect).
|
||||
/// </summary>
|
||||
@@ -3494,6 +3512,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Next Session.
|
||||
/// </summary>
|
||||
internal static string NextSession {
|
||||
get {
|
||||
return ResourceManager.GetString("NextSession", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No.
|
||||
/// </summary>
|
||||
@@ -3963,6 +3990,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Previous Session.
|
||||
/// </summary>
|
||||
internal static string PreviousSession {
|
||||
get {
|
||||
return ResourceManager.GetString("PreviousSession", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Printers.
|
||||
/// </summary>
|
||||
|
||||
@@ -120,6 +120,18 @@
|
||||
<data name="MenuItem_About" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="_Sessions" xml:space="preserve">
|
||||
<value>&Sessions</value>
|
||||
</data>
|
||||
<data name="NextSession" xml:space="preserve">
|
||||
<value>Next Session</value>
|
||||
</data>
|
||||
<data name="PreviousSession" xml:space="preserve">
|
||||
<value>Previous Session</value>
|
||||
</data>
|
||||
<data name="JumpToSession" xml:space="preserve">
|
||||
<value>Jump to Session {0}</value>
|
||||
</data>
|
||||
<data name="ActiveDirectory" xml:space="preserve">
|
||||
<value>Active Directory</value>
|
||||
</data>
|
||||
@@ -1102,10 +1114,10 @@ If you run into such an error, please create a new connection file!</value>
|
||||
<value>Use the Credential Security Support Provider (CredSSP) for authentication if it is available.</value>
|
||||
</data>
|
||||
<data name="PropertyDescriptionUseRestrictedAdmin" xml:space="preserve">
|
||||
<value>Use restricted admin mode on the target host (local system context).</value>
|
||||
<value>Use restricted admin mode on the target host (local system context). Credentials are not forwarded to the remote host; the current user's Kerberos ticket is used instead. Recommended for AD Protected Users accounts where NTLM authentication is disabled. Requires the connecting user to have administrative rights on the target.</value>
|
||||
</data>
|
||||
<data name="PropertyDescriptionUseRCG" xml:space="preserve">
|
||||
<value>Use Remote Credential Guard to tunnel authentication on target back to source through the RDP channel.</value>
|
||||
<value>Use Remote Credential Guard to tunnel authentication on target back to source through the RDP channel. Kerberos requests are redirected back to the connecting device, so credentials are never sent to the remote host. Recommended for AD Protected Users accounts where NTLM authentication is disabled. Requires both client and server to be domain-joined.</value>
|
||||
</data>
|
||||
<data name="PropertyDescriptionUser1" xml:space="preserve">
|
||||
<value>Feel free to enter any information you need here.</value>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
|
||||
|
||||
//Generated for platform: x64
|
||||
|
||||
@@ -11,7 +10,7 @@ using System.Resources;
|
||||
|
||||
// Compute version values
|
||||
|
||||
//Build nr: 3225
|
||||
//Build nr: 3405
|
||||
|
||||
// General Information
|
||||
[assembly: AssemblyTitle("mRemoteNG")]
|
||||
@@ -19,12 +18,12 @@ using System.Resources;
|
||||
[assembly: AssemblyConfiguration("x64")]
|
||||
[assembly: AssemblyCompany("Profi-KOM Ltd.")]
|
||||
[assembly: AssemblyProduct("mRemoteNG Connection Manager")]
|
||||
[assembly: AssemblyCopyright("(c) 2025 mRemoteNG")]
|
||||
[assembly: AssemblyCopyright("(c) 2026 mRemoteNG")]
|
||||
[assembly: AssemblyTrademark("Profi-KOM LTd.")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.78.2.3225")]
|
||||
[assembly: AssemblyFileVersion("1.78.2.3225")]
|
||||
[assembly: AssemblyVersion("1.78.2.3405")]
|
||||
[assembly: AssemblyFileVersion("1.78.2.3405")]
|
||||
[assembly: NeutralResourcesLanguageAttribute("en-US")]
|
||||
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3225) x64")]
|
||||
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3405) x64")]
|
||||
@@ -11,14 +11,30 @@
|
||||
if (File.Exists(dllPath)) {
|
||||
Assembly.LoadFrom(dllPath);
|
||||
var sp = Host as IServiceProvider;
|
||||
var dte = sp?.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
|
||||
var projectItem = dte?.Solution?.FindProjectItem(Host.TemplateFile);
|
||||
var project = projectItem?.ContainingProject;
|
||||
var cfg = project?.ConfigurationManager?.ActiveConfiguration;
|
||||
platformFromDte = cfg?.PlatformName;
|
||||
if (sp != null) {
|
||||
// Use reflection to avoid compile-time dependency on EnvDTE
|
||||
var envDteAsm = AppDomain.CurrentDomain.GetAssemblies();
|
||||
Type dteType = null;
|
||||
foreach (var asm in envDteAsm) {
|
||||
dteType = asm.GetType("EnvDTE.DTE");
|
||||
if (dteType != null) break;
|
||||
}
|
||||
if (dteType != null) {
|
||||
var dte = sp.GetService(dteType);
|
||||
if (dte != null) {
|
||||
var solution = dteType.GetProperty("Solution")?.GetValue(dte);
|
||||
var findItem = solution?.GetType().GetMethod("FindProjectItem", new[] { typeof(string) });
|
||||
var projectItem = findItem?.Invoke(solution, new object[] { Host.TemplateFile });
|
||||
var project = projectItem?.GetType().GetProperty("ContainingProject")?.GetValue(projectItem);
|
||||
var cfgMgr = project?.GetType().GetProperty("ConfigurationManager")?.GetValue(project);
|
||||
var activeCfg = cfgMgr?.GetType().GetProperty("ActiveConfiguration")?.GetValue(cfgMgr);
|
||||
platformFromDte = activeCfg?.GetType().GetProperty("PlatformName")?.GetValue(activeCfg) as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Log the exception if necessary
|
||||
// Fallback to environment variables below
|
||||
}
|
||||
|
||||
platformValue = platformFromDte
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace mRemoteNG.Properties {
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool PopupMessageWriterWriteErrorMsgs {
|
||||
get {
|
||||
return ((bool)(this["PopupMessageWriterWriteErrorMsgs"]));
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="PopupMessageWriterWriteErrorMsgs" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="LogToApplicationDirectory" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
|
||||
@@ -46,17 +46,19 @@ namespace mRemoteNG.Tools
|
||||
return Status;
|
||||
}
|
||||
|
||||
//X509Certificate2 certificate2 = new(X509Certificate.CreateFromSignedFile(FilePath));
|
||||
#pragma warning disable SYSLIB0057
|
||||
using X509Certificate cert = X509Certificate.CreateFromSignedFile(FilePath);
|
||||
byte[] certData = cert.GetRawCertData();
|
||||
#pragma warning restore SYSLIB0057
|
||||
X509Certificate2 certificate2 = X509CertificateLoader.LoadCertificate(certData);
|
||||
_thumbprint = certificate2.Thumbprint;
|
||||
|
||||
X509Certificate2 certificate2 = new(X509Certificate.CreateFromSignedFile(FilePath));
|
||||
_thumbprint = certificate2.Thumbprint;
|
||||
|
||||
if (_thumbprint != ThumbprintToMatch)
|
||||
{
|
||||
Status = StatusValue.ThumbprintNotMatch;
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
if (_thumbprint != ThumbprintToMatch)
|
||||
{
|
||||
Status = StatusValue.ThumbprintNotMatch;
|
||||
return Status;
|
||||
}
|
||||
}
|
||||
|
||||
NativeMethods.WINTRUST_FILE_INFO trustFileInfo = new() { pcwszFilePath = FilePath};
|
||||
trustFileInfoPointer = Marshal.AllocCoTaskMem(Marshal.SizeOf(trustFileInfo));
|
||||
|
||||
@@ -42,17 +42,17 @@ namespace mRemoteNG.Tools
|
||||
return result;
|
||||
}
|
||||
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
return new StandardValuesCollection(SshTunnels);
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mRemoteNG.UI.Controls
|
||||
private MrngLabel label2;
|
||||
private MrngLabel label3;
|
||||
private ToolTip toolTip1;
|
||||
private System.ComponentModel.IContainer components;
|
||||
private System.ComponentModel.Container components;
|
||||
|
||||
/* Sets and Gets the tooltiptext on toolTip1 */
|
||||
public string ToolTipText
|
||||
@@ -46,7 +46,7 @@ namespace mRemoteNG.UI.Controls
|
||||
}
|
||||
|
||||
/* Set or Get the string that represents the value in the box */
|
||||
public override string? Text
|
||||
public override string Text
|
||||
{
|
||||
get => (Octet1.Text ?? string.Empty) + @"." + (Octet2.Text ?? string.Empty) + @"." + (Octet3.Text ?? string.Empty) + @"." + (Octet4.Text ?? string.Empty);
|
||||
set
|
||||
@@ -119,9 +119,7 @@ namespace mRemoteNG.UI.Controls
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// ReSharper disable once UseNullPropagation
|
||||
if (components != null)
|
||||
components.Dispose();
|
||||
components?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.App.Info;
|
||||
using mRemoteNG.Themes;
|
||||
@@ -8,6 +9,8 @@ using mRemoteNG.Properties;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.UI.Window;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Messages;
|
||||
|
||||
namespace mRemoteNG.UI.Forms
|
||||
{
|
||||
@@ -41,7 +44,7 @@ namespace mRemoteNG.UI.Forms
|
||||
[Conditional("PORTABLE")]
|
||||
private void AddPortableString() => lblTitle.Text += $@" {Language.PortableEdition}";
|
||||
|
||||
private void ApplyTheme()
|
||||
private new void ApplyTheme()
|
||||
{
|
||||
if (!ThemeManager.getInstance().ThemingActive) return;
|
||||
if (!ThemeManager.getInstance().ActiveAndExtended) return;
|
||||
@@ -59,47 +62,110 @@ namespace mRemoteNG.UI.Forms
|
||||
|
||||
private void llLicense_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/COPYING.txt");
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
|
||||
if (version != null && updateChannel != null)
|
||||
{
|
||||
var versionString = version.ToString();
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/COPYING.txt");
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
private void llChangelog_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/CHANGELOG.md");
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
|
||||
if (version != null && updateChannel != null)
|
||||
{
|
||||
var versionString = version.ToString();
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/CHANGELOG.md");
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
private void llCredits_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/CREDITS.md");
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
|
||||
if (version != null && updateChannel != null)
|
||||
{
|
||||
var versionString = version.ToString();
|
||||
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/CREDITS.md");
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OpenUrl(string url)
|
||||
private static void OpenUrl(string url)
|
||||
{
|
||||
// Validate URL format to prevent injection
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
return;
|
||||
|
||||
// Basic URL validation - ensure it starts with http:// or https://
|
||||
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Invalid URL format - don't try to open it
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(url);
|
||||
// Use the standard .NET approach for opening URLs securely
|
||||
// UseShellExecute=true delegates to the OS default handler
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// hack because of this: https://github.com/dotnet/corefx/issues/10361
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
// Fallback for older .NET Core versions with bug: https://github.com/dotnet/corefx/issues/10361
|
||||
// Use platform-specific URL launchers
|
||||
try
|
||||
{
|
||||
url = url.Replace("&", "^&");
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Use rundll32 with url.dll as fallback
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "rundll32.exe",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
startInfo.ArgumentList.Add("url.dll,FileProtocolHandler");
|
||||
startInfo.ArgumentList.Add(url);
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "xdg-open",
|
||||
UseShellExecute = false
|
||||
};
|
||||
startInfo.ArgumentList.Add(url);
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "open",
|
||||
UseShellExecute = false
|
||||
};
|
||||
startInfo.ArgumentList.Add(url);
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
catch
|
||||
{
|
||||
Process.Start("xdg-open", url);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
// Unable to open URL - notify the user
|
||||
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
|
||||
"Unable to open URL in browser. Please open manually: " + url, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
mRemoteNG/UI/Forms/FrmInputBox.Designer.cs
generated
20
mRemoteNG/UI/Forms/FrmInputBox.Designer.cs
generated
@@ -44,6 +44,8 @@ namespace mRemoteNG.UI.Forms
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
this.tableLayoutPanel1.AutoSize = true;
|
||||
this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.tableLayoutPanel1.Controls.Add(this._Ok, 1, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.buttonCancel, 2, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.textBox, 0, 1);
|
||||
@@ -51,11 +53,12 @@ namespace mRemoteNG.UI.Forms
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(8);
|
||||
this.tableLayoutPanel1.RowCount = 3;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(284, 81);
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F));
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(284, 90);
|
||||
this.tableLayoutPanel1.TabIndex = 0;
|
||||
//
|
||||
// _Ok
|
||||
@@ -103,12 +106,14 @@ namespace mRemoteNG.UI.Forms
|
||||
this.label.TabIndex = 3;
|
||||
this.label.Text = "Label";
|
||||
this.label.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
//
|
||||
// FrmInputBox
|
||||
//
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(284, 81);
|
||||
this.AutoSize = true;
|
||||
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.ClientSize = new System.Drawing.Size(300, 95);
|
||||
this.ControlBox = false;
|
||||
this.Controls.Add(this.tableLayoutPanel1);
|
||||
this.DoubleBuffered = true;
|
||||
@@ -116,6 +121,7 @@ namespace mRemoteNG.UI.Forms
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.MinimumSize = new System.Drawing.Size(300, 95);
|
||||
this.Name = "FrmInputBox";
|
||||
this.ShowIcon = false;
|
||||
this.ShowInTaskbar = false;
|
||||
|
||||
@@ -91,7 +91,12 @@ namespace mRemoteNG.UI.Forms
|
||||
|
||||
private void buttonCreateBug_Click(object sender, EventArgs e)
|
||||
{
|
||||
Process.Start(GeneralAppInfo.UrlBugs);
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = GeneralAppInfo.UrlBugs,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using mRemoteNG.App;
|
||||
using mRemoteNG.Config.Settings.Registry;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using mRemoteNG.Tools;
|
||||
|
||||
namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
@@ -342,8 +343,17 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate path to prevent command injection
|
||||
PathValidator.ValidatePathOrThrow(path, nameof(path));
|
||||
|
||||
// Open the file using the default application associated with its file type based on the user's preference
|
||||
Process.Start(path);
|
||||
// Use ProcessStartInfo with UseShellExecute for better control
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -362,9 +372,19 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate path to prevent command injection
|
||||
PathValidator.ValidatePathOrThrow(path, nameof(path));
|
||||
|
||||
// Open it in "Notepad" (Windows default editor).
|
||||
// Usually available on all Windows systems
|
||||
Process.Start("notepad.exe", path);
|
||||
// Use ProcessStartInfo with ArgumentList for better security
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "notepad.exe",
|
||||
UseShellExecute = false
|
||||
};
|
||||
startInfo.ArgumentList.Add(path);
|
||||
Process.Start(startInfo);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -383,11 +403,22 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate path to prevent command injection
|
||||
PathValidator.ValidatePathOrThrow(path, nameof(path));
|
||||
|
||||
// when all fails open filelocation to logfile...
|
||||
// Open Windows Explorer to the directory containing the file
|
||||
Process.Start("explorer.exe", $"/select,\"{path}\"");
|
||||
return true;
|
||||
}
|
||||
// Explorer expects /select,"path" as a single argument
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
UseShellExecute = false
|
||||
};
|
||||
startInfo.ArgumentList.Add("/select,");
|
||||
startInfo.ArgumentList.Add(path);
|
||||
Process.Start(startInfo);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If necessary, the error can be logged here.
|
||||
|
||||
12
mRemoteNG/UI/Forms/frmMain.Designer.cs
generated
12
mRemoteNG/UI/Forms/frmMain.Designer.cs
generated
@@ -38,6 +38,7 @@ namespace mRemoteNG.UI.Forms
|
||||
this.pnlDock = new WeifenLuo.WinFormsUI.Docking.DockPanel();
|
||||
this.msMain = new System.Windows.Forms.MenuStrip();
|
||||
this.fileMenu = new mRemoteNG.UI.Menu.FileMenu();
|
||||
this.sessionsMenu = new mRemoteNG.UI.Menu.SessionsMenu();
|
||||
this.viewMenu = new mRemoteNG.UI.Menu.ViewMenu();
|
||||
this.toolsMenu = new mRemoteNG.UI.Menu.ToolsMenu();
|
||||
this.helpMenu = new mRemoteNG.UI.Menu.HelpMenu();
|
||||
@@ -75,13 +76,14 @@ namespace mRemoteNG.UI.Forms
|
||||
this.msMain.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible;
|
||||
this.msMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.fileMenu,
|
||||
this.sessionsMenu,
|
||||
this.viewMenu,
|
||||
this.toolsMenu,
|
||||
this.helpMenu});
|
||||
this.msMain.Location = new System.Drawing.Point(3, 0);
|
||||
this.msMain.Name = "msMain";
|
||||
this.msMain.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0);
|
||||
this.msMain.Size = new System.Drawing.Size(151, 25);
|
||||
this.msMain.Size = new System.Drawing.Size(212, 25);
|
||||
this.msMain.Stretch = false;
|
||||
this.msMain.TabIndex = 0;
|
||||
this.msMain.Text = "Main Toolbar";
|
||||
@@ -94,6 +96,13 @@ namespace mRemoteNG.UI.Forms
|
||||
this.fileMenu.Text = "&File";
|
||||
this.fileMenu.TreeWindow = null;
|
||||
//
|
||||
// sessionsMenu
|
||||
//
|
||||
this.sessionsMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
this.sessionsMenu.Name = "mMenSessions";
|
||||
this.sessionsMenu.Size = new System.Drawing.Size(61, 19);
|
||||
this.sessionsMenu.Text = "&Sessions";
|
||||
//
|
||||
// viewMenu
|
||||
//
|
||||
this.viewMenu.FullscreenHandler = null;
|
||||
@@ -253,6 +262,7 @@ namespace mRemoteNG.UI.Forms
|
||||
internal System.Windows.Forms.ToolStripSeparator mMenSep3;
|
||||
private System.ComponentModel.IContainer components;
|
||||
private Menu.FileMenu fileMenu;
|
||||
private Menu.SessionsMenu sessionsMenu;
|
||||
private Menu.ViewMenu viewMenu;
|
||||
private Menu.ToolsMenu toolsMenu;
|
||||
private Menu.HelpMenu helpMenu;
|
||||
|
||||
@@ -234,6 +234,8 @@ namespace mRemoteNG.UI.Forms
|
||||
else
|
||||
SetLayout();
|
||||
|
||||
ShowHidePanelTabs();
|
||||
|
||||
Runtime.ConnectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
|
||||
Runtime.ConnectionsService.ConnectionsSaved += ConnectionsServiceOnConnectionsSaved;
|
||||
|
||||
@@ -292,6 +294,7 @@ namespace mRemoteNG.UI.Forms
|
||||
private void ApplyLanguage()
|
||||
{
|
||||
fileMenu.ApplyLanguage();
|
||||
sessionsMenu.ApplyLanguage();
|
||||
viewMenu.ApplyLanguage();
|
||||
toolsMenu.ApplyLanguage();
|
||||
helpMenu.ApplyLanguage();
|
||||
@@ -398,6 +401,11 @@ namespace mRemoteNG.UI.Forms
|
||||
|
||||
private async void FrmMain_Shown(object sender, EventArgs e)
|
||||
{
|
||||
// Bring the main window to the front after splash screen closes
|
||||
Activate();
|
||||
BringToFront();
|
||||
NativeMethods.SetForegroundWindow(Handle);
|
||||
|
||||
PromptForUpdatesPreference();
|
||||
await CheckForUpdates();
|
||||
}
|
||||
@@ -692,6 +700,7 @@ namespace mRemoteNG.UI.Forms
|
||||
private void PnlDock_ActiveDocumentChanged(object sender, EventArgs e)
|
||||
{
|
||||
ActivateConnection();
|
||||
sessionsMenu.UpdateMenuState();
|
||||
}
|
||||
|
||||
internal void UpdateWindowTitle()
|
||||
|
||||
@@ -188,19 +188,29 @@ namespace mRemoteNG.UI.Menu
|
||||
}
|
||||
}
|
||||
|
||||
private void mMenInfoHelp_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDocumentation);
|
||||
private void mMenInfoHelp_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDocumentation);
|
||||
|
||||
private void mMenInfoForum_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlForum);
|
||||
private void mMenInfoForum_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlForum);
|
||||
|
||||
private void mMenInfoChat_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlChat);
|
||||
private void mMenInfoChat_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlChat);
|
||||
|
||||
private void mMenInfoCommunity_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlCommunity);
|
||||
private void mMenInfoCommunity_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlCommunity);
|
||||
|
||||
private void mMenInfoBug_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlBugs);
|
||||
private void mMenInfoBug_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlBugs);
|
||||
|
||||
private void mMenInfoWebsite_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlHome);
|
||||
private void mMenInfoWebsite_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlHome);
|
||||
|
||||
private void mMenInfoDonate_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDonate);
|
||||
private void mMenInfoDonate_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDonate);
|
||||
|
||||
private static void OpenUrl(string url)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
private void mMenInfoAbout_Click(object? sender, EventArgs e)
|
||||
{
|
||||
|
||||
161
mRemoteNG/UI/Menu/msMain/SessionsMenu.cs
Normal file
161
mRemoteNG/UI/Menu/msMain/SessionsMenu.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using mRemoteNG.UI.Window;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
namespace mRemoteNG.UI.Menu
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SessionsMenu : ToolStripMenuItem
|
||||
{
|
||||
private ToolStripMenuItem _mMenSessionsNextSession;
|
||||
private ToolStripMenuItem _mMenSessionsPreviousSession;
|
||||
private ToolStripSeparator _mMenSessionsSep1;
|
||||
private readonly ToolStripMenuItem[] _sessionNumberItems = new ToolStripMenuItem[9];
|
||||
|
||||
public SessionsMenu()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_mMenSessionsNextSession = new ToolStripMenuItem();
|
||||
_mMenSessionsPreviousSession = new ToolStripMenuItem();
|
||||
_mMenSessionsSep1 = new ToolStripSeparator();
|
||||
|
||||
// Initialize session number menu items (Ctrl+1 through Ctrl+9)
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
_sessionNumberItems[i] = new ToolStripMenuItem();
|
||||
}
|
||||
|
||||
//
|
||||
// mMenSessions
|
||||
//
|
||||
DropDownItems.Add(_mMenSessionsNextSession);
|
||||
DropDownItems.Add(_mMenSessionsPreviousSession);
|
||||
DropDownItems.Add(_mMenSessionsSep1);
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
DropDownItems.Add(_sessionNumberItems[i]);
|
||||
}
|
||||
|
||||
Name = "mMenSessions";
|
||||
Size = new System.Drawing.Size(61, 20);
|
||||
Text = Language._Sessions;
|
||||
|
||||
//
|
||||
// mMenSessionsNextSession
|
||||
//
|
||||
_mMenSessionsNextSession.Name = "mMenSessionsNextSession";
|
||||
_mMenSessionsNextSession.ShortcutKeys = Keys.Control | Keys.Right;
|
||||
_mMenSessionsNextSession.Size = new System.Drawing.Size(230, 22);
|
||||
_mMenSessionsNextSession.Text = Language.NextSession;
|
||||
_mMenSessionsNextSession.Click += mMenSessionsNextSession_Click;
|
||||
|
||||
//
|
||||
// mMenSessionsPreviousSession
|
||||
//
|
||||
_mMenSessionsPreviousSession.Name = "mMenSessionsPreviousSession";
|
||||
_mMenSessionsPreviousSession.ShortcutKeys = Keys.Control | Keys.Left;
|
||||
_mMenSessionsPreviousSession.Size = new System.Drawing.Size(230, 22);
|
||||
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
|
||||
_mMenSessionsPreviousSession.Click += mMenSessionsPreviousSession_Click;
|
||||
|
||||
//
|
||||
// mMenSessionsSep1
|
||||
//
|
||||
_mMenSessionsSep1.Name = "mMenSessionsSep1";
|
||||
_mMenSessionsSep1.Size = new System.Drawing.Size(227, 6);
|
||||
|
||||
// Initialize session number items (Ctrl+1 through Ctrl+9)
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
int sessionNumber = i + 1;
|
||||
_sessionNumberItems[i].Name = $"mMenSessionsSession{sessionNumber}";
|
||||
_sessionNumberItems[i].ShortcutKeys = Keys.Control | (Keys)((int)Keys.D1 + i);
|
||||
_sessionNumberItems[i].Size = new System.Drawing.Size(230, 22);
|
||||
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), sessionNumber);
|
||||
_sessionNumberItems[i].Enabled = false; // Initialize as disabled
|
||||
int capturedIndex = i; // Capture the index for the lambda
|
||||
_sessionNumberItems[i].Click += (s, e) => JumpToSessionNumber(capturedIndex);
|
||||
}
|
||||
|
||||
// Initialize navigation items as disabled
|
||||
_mMenSessionsNextSession.Enabled = false;
|
||||
_mMenSessionsPreviousSession.Enabled = false;
|
||||
|
||||
// Hook up the dropdown opening event to update enabled state
|
||||
DropDownOpening += SessionsMenu_DropDownOpening;
|
||||
}
|
||||
|
||||
public void ApplyLanguage()
|
||||
{
|
||||
Text = Language._Sessions;
|
||||
_mMenSessionsNextSession.Text = Language.NextSession;
|
||||
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateMenuState()
|
||||
{
|
||||
// Update enabled state of menu items based on active sessions
|
||||
var connectionWindow = GetActiveConnectionWindow();
|
||||
bool hasMultipleSessions = false;
|
||||
int sessionCount = 0;
|
||||
|
||||
if (connectionWindow != null)
|
||||
{
|
||||
var documents = connectionWindow.GetDocuments();
|
||||
sessionCount = documents.Length;
|
||||
hasMultipleSessions = sessionCount > 1;
|
||||
}
|
||||
|
||||
_mMenSessionsNextSession.Enabled = hasMultipleSessions;
|
||||
_mMenSessionsPreviousSession.Enabled = hasMultipleSessions;
|
||||
|
||||
// Enable/disable session number items based on session count
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
_sessionNumberItems[i].Enabled = (i < sessionCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionsMenu_DropDownOpening(object sender, EventArgs e)
|
||||
{
|
||||
// Update state when menu is opened (for visual feedback)
|
||||
UpdateMenuState();
|
||||
}
|
||||
|
||||
private void mMenSessionsNextSession_Click(object sender, EventArgs e)
|
||||
{
|
||||
var connectionWindow = GetActiveConnectionWindow();
|
||||
connectionWindow?.NavigateToNextTab();
|
||||
}
|
||||
|
||||
private void mMenSessionsPreviousSession_Click(object sender, EventArgs e)
|
||||
{
|
||||
var connectionWindow = GetActiveConnectionWindow();
|
||||
connectionWindow?.NavigateToPreviousTab();
|
||||
}
|
||||
|
||||
private void JumpToSessionNumber(int index)
|
||||
{
|
||||
var connectionWindow = GetActiveConnectionWindow();
|
||||
connectionWindow?.NavigateToTab(index);
|
||||
}
|
||||
|
||||
private ConnectionWindow GetActiveConnectionWindow()
|
||||
{
|
||||
return FrmMain.Default.pnlDock?.ActiveDocument as ConnectionWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace mRemoteNG.UI.TaskDialog
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Override this to make sure the control is invalidated (repainted) when 'Text' is changed
|
||||
public override string Text
|
||||
public override string? Text
|
||||
{
|
||||
get => base.Text;
|
||||
set
|
||||
@@ -109,13 +109,16 @@ namespace mRemoteNG.UI.TaskDialog
|
||||
//--------------------------------------------------------------------------------
|
||||
string GetLargeText()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Text))
|
||||
return string.Empty;
|
||||
|
||||
string[] lines = Text.Split('\n');
|
||||
return lines[0];
|
||||
}
|
||||
|
||||
string GetSmallText()
|
||||
{
|
||||
if (Text.IndexOf('\n') < 0)
|
||||
if (string.IsNullOrEmpty(Text) || Text.IndexOf('\n') < 0)
|
||||
return "";
|
||||
|
||||
string s = Text;
|
||||
|
||||
@@ -387,6 +387,34 @@ namespace mRemoteNG.UI.Window
|
||||
}
|
||||
}
|
||||
|
||||
internal void NavigateToTab(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documents = connDock.DocumentsToArray();
|
||||
if (index < 0 || index >= documents.Length) return;
|
||||
|
||||
documents[index].DockHandler.Activate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddExceptionMessage("NavigateToTab (UI.Window.ConnectionWindow) failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal IDockContent[] GetDocuments()
|
||||
{
|
||||
try
|
||||
{
|
||||
return connDock.DocumentsToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddExceptionMessage("GetDocuments (UI.Window.ConnectionWindow) failed", ex);
|
||||
return Array.Empty<IDockContent>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
@@ -99,7 +99,12 @@ namespace mRemoteNG.UI.Window
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Start(linkUri.ToString());
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = linkUri.ToString(),
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
<PropertyGroup>
|
||||
<Platforms>x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<TransformOnBuild>true</TransformOnBuild>
|
||||
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
|
||||
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
|
||||
<ApplicationIcon>Icons\mRemoteNG.ico</ApplicationIcon>
|
||||
<Version>1.78.2-dev</Version>
|
||||
<Description>Multi-protocol remote connections manager</Description>
|
||||
@@ -15,13 +19,13 @@
|
||||
<PackageLicenseFile>COPYING.TXT</PackageLicenseFile>
|
||||
<PackageProjectUrl>https://mremoteng.org/</PackageProjectUrl>
|
||||
<Platforms>x64;arm64</Platforms>
|
||||
<Configurations>Debug;Release;Debug Portable;Release Portable;Release Installer;Deploy to github</Configurations>
|
||||
<Configurations>Debug;Release;Release Self-Contained;Deploy to github</Configurations>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PackageIcon>Header_dark.png</PackageIcon>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<UseWPF>True</UseWPF>
|
||||
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<SignAssembly>False</SignAssembly>
|
||||
<Title>Multi-Remote Next Generation Connection Manager</Title>
|
||||
<RepositoryUrl>https://github.com/mRemoteNG/mRemoteNG.git</RepositoryUrl>
|
||||
@@ -69,55 +73,40 @@
|
||||
<WarningLevel>8</WarningLevel>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Portable|x64'">
|
||||
<DefineConstants>DEBUG;PORTABLE</DefineConstants>
|
||||
<Optimize>False</Optimize>
|
||||
<OutputPath>bin\x64\Debug Portable\</OutputPath>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Portable|arm64'">
|
||||
<DefineConstants>DEBUG;PORTABLE</DefineConstants>
|
||||
<Optimize>False</Optimize>
|
||||
<OutputPath>bin\arm64\Debug Portable\</OutputPath>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
|
||||
<DefineConstants>PORTABLE</DefineConstants>
|
||||
<Optimize>True</Optimize>
|
||||
<OutputPath>bin\x64\Release Portable\</OutputPath>
|
||||
<PublishDir>bin\x64\Publish Self-Contained\</PublishDir>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<CopyBuildOutputToPublishDirectory>true</CopyBuildOutputToPublishDirectory>
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
<RunCommand></RunCommand>
|
||||
<RunArguments></RunArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|arm64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
|
||||
<DefineConstants>PORTABLE</DefineConstants>
|
||||
<Optimize>True</Optimize>
|
||||
<OutputPath>bin\arm64\Release Portable\</OutputPath>
|
||||
<OutputPath>bin\arm64\Release Self-Contained\</OutputPath>
|
||||
<PublishDir>bin\arm64\Publish Self-Contained\</PublishDir>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Installer|x64'">
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<Optimize>True</Optimize>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Installer|arm64'">
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<Optimize>True</Optimize>
|
||||
<WarningLevel>8</WarningLevel>
|
||||
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<CopyBuildOutputToPublishDirectory>true</CopyBuildOutputToPublishDirectory>
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
<RunCommand></RunCommand>
|
||||
<RunArguments></RunArguments>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="buildenv.tmp" />
|
||||
@@ -149,59 +138,17 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<PackageReference Include="Microsoft.NETCore.Platforms" />
|
||||
<PackageReference Include="Microsoft.NETCore.Targets" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.TextTemplating.VSHost" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||
<PackageReference Include="MySql.Data" />
|
||||
<PackageReference Include="NETStandard.Library" />
|
||||
<PackageReference Include="System.Data.Odbc" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" />
|
||||
<PackageReference Include="OpenCover" />
|
||||
<PackageReference Include="Renci.SshNet.Async" />
|
||||
<PackageReference Include="ReportGenerator" />
|
||||
<PackageReference Include="runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.native.System" />
|
||||
<PackageReference Include="runtime.native.System.IO.Compression" />
|
||||
<PackageReference Include="runtime.native.System.Net.Http" />
|
||||
<PackageReference Include="runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="SSH.NET" />
|
||||
<PackageReference Include="System.Collections.Immutable" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" />
|
||||
<PackageReference Include="System.Console" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog" />
|
||||
<PackageReference Include="System.DirectoryServices" />
|
||||
<PackageReference Include="System.Drawing.Common" />
|
||||
<PackageReference Include="System.Formats.Asn1" />
|
||||
<PackageReference Include="System.IO.Pipelines" />
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="System.Net.Primitives" />
|
||||
<PackageReference Include="System.Net.Sockets" />
|
||||
<PackageReference Include="System.Reflection.Emit" />
|
||||
<PackageReference Include="System.Reflection.Emit.ILGeneration" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" />
|
||||
<PackageReference Include="System.Reflection.Metadata" />
|
||||
<PackageReference Include="System.Resources.ResourceManager" />
|
||||
<PackageReference Include="System.Runtime.Extensions" />
|
||||
<PackageReference Include="System.Security.AccessControl" />
|
||||
<PackageReference Include="System.Security.Cryptography.Algorithms" />
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" />
|
||||
<PackageReference Include="System.Security.Cryptography.OpenSsl" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||
<PackageReference Include="System.Security.Cryptography.X509Certificates" />
|
||||
<PackageReference Include="System.Security.Permissions" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" />
|
||||
<PackageReference Include="System.Windows.Extensions" />
|
||||
<PackageReference Include="System.Xml.ReaderWriter" />
|
||||
<PackageReference Include="VncSharpCore" />
|
||||
<PackageReference Include="ZstdSharp.Port" />
|
||||
</ItemGroup>
|
||||
@@ -334,6 +281,11 @@
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<T4ParameterValues Include="platformType">
|
||||
<Value>$(Platform)</Value>
|
||||
</T4ParameterValues>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Properties\AssemblyInfo.tt">
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
@@ -601,10 +553,10 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="143.0.9" />
|
||||
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="145.0.26" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Platform)'=='arm64'">
|
||||
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="143.0.9" />
|
||||
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="145.0.26" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
@@ -634,4 +586,15 @@
|
||||
<!-- Disable CET compatibility check in .NET 9 AppHost -->
|
||||
<CETCompat>false</CETCompat>
|
||||
</PropertyGroup>
|
||||
<Target Name="PublishAfterBuild" AfterTargets="Build" Condition="'$(Configuration)' == 'Release Self-Contained'">
|
||||
<Message Text="Publishing self-contained application for $(Platform)..." Importance="high" />
|
||||
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Publish" Properties="Configuration=$(Configuration);Platform=$(Platform);NoBuild=true;NoRestore=true" />
|
||||
<PropertyGroup>
|
||||
<TempBuildDir>$([System.IO.Path]::GetFullPath('$(OutputPath)'))</TempBuildDir>
|
||||
</PropertyGroup>
|
||||
<Message Text="Cleaning up temporary build directory: $(TempBuildDir)" Importance="high" />
|
||||
<RemoveDir Directories="$(TempBuildDir)" />
|
||||
<RemoveDir Directories="$(ProjectDir)bin\x64\Release Self-Contained" />
|
||||
</Target>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v18.0\TextTemplating\Microsoft.TextTemplating.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,10 @@ Common External Tool Configurations
|
||||
The list below of various examples is by no means a full list of ways to use
|
||||
**External Tools** but gives you a idea of how it can be used in different ways.
|
||||
|
||||
.. note::
|
||||
These examples use variables like %HOSTNAME%, %USERNAME%, etc.
|
||||
For a complete list of available variables, see the :ref:`variables_reference` page.
|
||||
|
||||
Ping
|
||||
====
|
||||
Ping a server via cmdline.
|
||||
|
||||
@@ -23,6 +23,8 @@ What we are going to use is the following for our entry:
|
||||
- Protocol - sftp://
|
||||
- Input Parameters (variables) - %HOSTNAME%, %USERNAME%,%PASSWORD% and %PORT%
|
||||
|
||||
For a complete list of available variables, see the :ref:`variables_reference` page.
|
||||
|
||||
All of the variables are parsed from mRemoteNG connection item to the filezilla command line.
|
||||
So lets build this entry up in **External Tools** where we add all these items.
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ Welcome to mRemoteNG's documentation!
|
||||
:maxdepth: 2
|
||||
:caption: Miscellaneous
|
||||
|
||||
variables_reference.rst
|
||||
external_tools_cheat_sheet.rst
|
||||
migrate.rst
|
||||
Contribute <https://github.com/mRemoteNG/mRemoteNG/wiki>
|
||||
|
||||
@@ -73,3 +73,35 @@ Connections
|
||||
- Move Up
|
||||
* - Ctrl+Down
|
||||
- Move Down
|
||||
|
||||
Sessions
|
||||
========
|
||||
|
||||
.. list-table::
|
||||
:widths: 30 70
|
||||
:header-rows: 1
|
||||
|
||||
* - Keybinding
|
||||
- Action
|
||||
* - Ctrl+Right
|
||||
- Next Session/Tab
|
||||
* - Ctrl+Left
|
||||
- Previous Session/Tab
|
||||
* - Ctrl+1
|
||||
- Jump to Session 1
|
||||
* - Ctrl+2
|
||||
- Jump to Session 2
|
||||
* - Ctrl+3
|
||||
- Jump to Session 3
|
||||
* - Ctrl+4
|
||||
- Jump to Session 4
|
||||
* - Ctrl+5
|
||||
- Jump to Session 5
|
||||
* - Ctrl+6
|
||||
- Jump to Session 6
|
||||
* - Ctrl+7
|
||||
- Jump to Session 7
|
||||
* - Ctrl+8
|
||||
- Jump to Session 8
|
||||
* - Ctrl+9
|
||||
- Jump to Session 9
|
||||
|
||||
@@ -100,7 +100,9 @@ Variables
|
||||
|
||||
Variables and arguments can be used to tell the external tool what to do.
|
||||
|
||||
This is the list of variables supported by mRemoteNG:
|
||||
For a complete list of available variables and their usage, see the :ref:`variables_reference` page.
|
||||
|
||||
Quick Reference - Available Variables:
|
||||
|
||||
- %NAME%
|
||||
- %HOSTNAME%
|
||||
@@ -112,53 +114,9 @@ This is the list of variables supported by mRemoteNG:
|
||||
- %MACADDRESS%
|
||||
- %USERFIELD%
|
||||
|
||||
mRemoteNG will also expand environment variables such as %PATH% and %USERPROFILE%. If you need to use an environment
|
||||
variable with the same name as an mRemoteNG variable, use \\% instead of %. The most common use of this is for the
|
||||
USERNAME environment variable. %USERNAME% will be expanded to the username set in the currently selected connection.
|
||||
\\%USERNAME\\% will be expanded to the value set in the USERNAME environment variable.
|
||||
See the :ref:`variables_reference` page for detailed information about:
|
||||
|
||||
If you need to send a variable name to a program without mRemoteNG expanding it, use ^% instead of %.
|
||||
mRemoteNG will remove the caret (^) and leave the rest unchanged.
|
||||
For example, ^%USERNAME^% will be sent to the program as %USERNAME% and will not be expanded.
|
||||
|
||||
Rules for variables
|
||||
-------------------
|
||||
- Variables always refer to the currently selected connection.
|
||||
- Variable names are case-insensitive.
|
||||
- Variables can be used in both the Filename and Arguments fields.
|
||||
|
||||
|
||||
Special Character Escaping
|
||||
==========================
|
||||
Expanded variables will be escaped using the rules below. There are two levels of escaping that are done.
|
||||
|
||||
1. Is escaping for standard argument splitting (C/C++ argv, CommandLineToArgvW, etc)
|
||||
2. Is escaping shell metacharacters for ShellExecute.
|
||||
|
||||
Argument splitting escaping
|
||||
---------------------------
|
||||
|
||||
- Each quotation mark will be escaped by a backslash
|
||||
- One or more backslashes (\\) followed by a quotation mark ("):
|
||||
- Each backslash will be escaped by another backslash
|
||||
- The quotation mark will be escaped by a backslash
|
||||
- If the connection's user field contains ``"This"`` is a ``\"test\"``
|
||||
- Then %USERFIELD% is replaced with ``\"This\"`` is a ``\\\"test\\\"``
|
||||
- A variable name followed by a quotation mark (for example, %USERFIELD%") with a value ending in one or more backslashes:
|
||||
- Each backslash will be escaped by another backslash
|
||||
- Example:
|
||||
- If the connection's user field contains ``c:\Example\``
|
||||
- Then "%USERFIELD%" is replaced with ``"c:\Example\\"``
|
||||
|
||||
To disable argument splitting escaping for a variable, precede its name with a minus (-) sign. For example: %-USERFIELD%
|
||||
|
||||
Shell metacharacter escaping
|
||||
----------------------------
|
||||
|
||||
- The shell metacharacters are ( ) % ! ^ " < > & |
|
||||
- Each shell metacharacter will be escaped by a caret (^)
|
||||
|
||||
To disable both argument splitting and shell metacharacter escaping for a variable, precede its name with an exclamation point (!).
|
||||
For example, %!USERFIELD%. This is not recommended and may cause unexpected results.
|
||||
|
||||
Only variables that have been expanded will be escaped. It is up to you to escape the rest of the arguments.
|
||||
- How to use environment variables
|
||||
- How to prevent variable expansion
|
||||
- Rules for using variables
|
||||
- Special character escaping
|
||||
|
||||
@@ -37,7 +37,7 @@ Popups settings
|
||||
|
||||
.. figure:: /images/notifications_popup.png
|
||||
|
||||
When items are selected here you will recieve a popup on the error that occurrs
|
||||
When items are selected here you will receive a popup on the error that occurs
|
||||
based on level chosen in settings here.
|
||||
This can be useful if you do not want to use the notification area
|
||||
and only get a popup if error occurs. (**default**: all off)
|
||||
and only get a popup if error occurs. (**default**: Errors enabled, Debug/Info/Warning disabled)
|
||||
|
||||
102
mRemoteNGDocumentation/variables_reference.rst
Normal file
102
mRemoteNGDocumentation/variables_reference.rst
Normal file
@@ -0,0 +1,102 @@
|
||||
.. _variables_reference:
|
||||
|
||||
*******************
|
||||
Variables Reference
|
||||
*******************
|
||||
|
||||
Variables (also called parameters) can be used with External Tools to dynamically insert connection properties into commands and arguments.
|
||||
|
||||
Available Variables
|
||||
===================
|
||||
|
||||
mRemoteNG supports the following variables:
|
||||
|
||||
%NAME%
|
||||
The display name of the connection.
|
||||
|
||||
%HOSTNAME%
|
||||
The hostname or IP address of the connection.
|
||||
|
||||
%PORT%
|
||||
The port number for the connection.
|
||||
|
||||
%USERNAME%
|
||||
The username configured for the connection.
|
||||
|
||||
%PASSWORD%
|
||||
The password configured for the connection.
|
||||
|
||||
%DOMAIN%
|
||||
The domain configured for the connection.
|
||||
|
||||
%DESCRIPTION%
|
||||
The description text of the connection.
|
||||
|
||||
%MACADDRESS%
|
||||
The MAC address configured for the connection.
|
||||
|
||||
%USERFIELD%
|
||||
The custom user field configured for the connection.
|
||||
|
||||
Environment Variables
|
||||
======================
|
||||
|
||||
mRemoteNG will also expand environment variables such as %PATH% and %USERPROFILE%. If you need to use an environment
|
||||
variable with the same name as an mRemoteNG variable, use \\% instead of %. The most common use of this is for the
|
||||
USERNAME environment variable. %USERNAME% will be expanded to the username set in the currently selected connection.
|
||||
\\%USERNAME\\% will be expanded to the value set in the USERNAME environment variable.
|
||||
|
||||
Preventing Variable Expansion
|
||||
==============================
|
||||
|
||||
If you need to send a variable name to a program without mRemoteNG expanding it, use ^% instead of %.
|
||||
mRemoteNG will remove the caret (^) and leave the rest unchanged.
|
||||
For example, ^%USERNAME^% will be sent to the program as %USERNAME% and will not be expanded.
|
||||
|
||||
Rules for Variables
|
||||
===================
|
||||
|
||||
- Variables always refer to the currently selected connection.
|
||||
- Variable names are case-insensitive.
|
||||
- Variables can be used in both the Filename and Arguments fields in External Tools.
|
||||
|
||||
Special Character Escaping
|
||||
===========================
|
||||
|
||||
Expanded variables will be escaped using the rules below. There are two levels of escaping that are done:
|
||||
|
||||
1. Escaping for standard argument splitting (C/C++ argv, CommandLineToArgvW, etc)
|
||||
2. Escaping shell metacharacters for ShellExecute.
|
||||
|
||||
Argument Splitting Escaping
|
||||
----------------------------
|
||||
|
||||
- Each quotation mark will be escaped by a backslash
|
||||
- One or more backslashes (\\) followed by a quotation mark ("):
|
||||
- Each backslash will be escaped by another backslash
|
||||
- The quotation mark will be escaped by a backslash
|
||||
- If the connection's user field contains ``"This"`` is a ``\"test\"``
|
||||
- Then %USERFIELD% is replaced with ``\"This\"`` is a ``\\\"test\\\"``
|
||||
- A variable name followed by a quotation mark (for example, %USERFIELD%") with a value ending in one or more backslashes:
|
||||
- Each backslash will be escaped by another backslash
|
||||
- Example:
|
||||
- If the connection's user field contains ``c:\Example\``
|
||||
- Then "%USERFIELD%" is replaced with ``"c:\Example\\"``
|
||||
|
||||
To disable argument splitting escaping for a variable, precede its name with a minus (-) sign. For example: %-USERFIELD%
|
||||
|
||||
Shell Metacharacter Escaping
|
||||
-----------------------------
|
||||
|
||||
- The shell metacharacters are ( ) % ! ^ " < > & |
|
||||
- Each shell metacharacter will be escaped by a caret (^)
|
||||
|
||||
To disable both argument splitting and shell metacharacter escaping for a variable, precede its name with an exclamation point (!).
|
||||
For example, %!USERFIELD%. This is not recommended and may cause unexpected results.
|
||||
|
||||
Only variables that have been expanded will be escaped. It is up to you to escape the rest of the arguments.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
- :ref:`external_tools` - For information on using variables with External Tools
|
||||
@@ -2,12 +2,14 @@
|
||||
using System.Management;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CustomActions
|
||||
{
|
||||
public class InstalledWindowsUpdateChecker
|
||||
{
|
||||
private readonly ManagementScope _managementScope;
|
||||
private static readonly Regex KbPattern = new Regex(@"^(KB)?\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public InstalledWindowsUpdateChecker()
|
||||
{
|
||||
@@ -61,12 +63,45 @@ namespace CustomActions
|
||||
var counter = 0;
|
||||
foreach (var kb in kbList)
|
||||
{
|
||||
var sanitizedKb = SanitizeKbId(kb);
|
||||
if (string.IsNullOrEmpty(sanitizedKb))
|
||||
continue; // Skip invalid KB IDs
|
||||
|
||||
if (counter > 0)
|
||||
whereClause += " OR ";
|
||||
whereClause += $"HotFixID='{kb}'";
|
||||
whereClause += $"HotFixID='{sanitizedKb}'";
|
||||
counter++;
|
||||
}
|
||||
return whereClause;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes a KB ID to prevent WQL injection attacks.
|
||||
/// KB IDs must match the pattern: optional "KB" prefix followed by digits,
|
||||
/// or just digits. Any other characters are rejected.
|
||||
/// </summary>
|
||||
/// <param name="kbId">The KB ID to sanitize</param>
|
||||
/// <returns>The sanitized KB ID, or empty string if invalid</returns>
|
||||
private string SanitizeKbId(string kbId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kbId))
|
||||
return string.Empty;
|
||||
|
||||
// KB IDs should match the pattern: optional KB prefix followed by digits
|
||||
// (e.g., KB1234567 or 1234567)
|
||||
// Trim whitespace and check if it matches the expected pattern
|
||||
var trimmedKb = kbId.Trim();
|
||||
if (!KbPattern.IsMatch(trimmedKb))
|
||||
return string.Empty;
|
||||
|
||||
// Normalize to uppercase
|
||||
var normalizedKb = trimmedKb.ToUpperInvariant();
|
||||
|
||||
// Ensure KB prefix is present (Win32_QuickFixEngineering always uses the KB prefix)
|
||||
if (!normalizedKb.StartsWith("KB"))
|
||||
normalizedKb = "KB" + normalizedKb;
|
||||
|
||||
return normalizedKb;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
mRemoteNGTests/Connection/ConnectionInitiatorTests.cs
Normal file
117
mRemoteNGTests/Connection/ConnectionInitiatorTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol;
|
||||
using mRemoteNG.Messages;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace mRemoteNGTests.Connection
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConnectionInitiatorTests
|
||||
{
|
||||
private ConnectionInitiator _connectionInitiator;
|
||||
private MessageCollector _messageCollector;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_connectionInitiator = new ConnectionInitiator();
|
||||
_messageCollector = Runtime.MessageCollector;
|
||||
_messageCollector.ClearMessages();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
_messageCollector?.ClearMessages();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OpenConnection_WithEmptyHostname_AddsErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var connectionInfo = new ConnectionInfo
|
||||
{
|
||||
Name = "Test Connection",
|
||||
Hostname = "", // Empty hostname
|
||||
Protocol = ProtocolType.RDP // RDP doesn't support blank hostname
|
||||
};
|
||||
|
||||
// Act
|
||||
_connectionInitiator.OpenConnection(connectionInfo);
|
||||
|
||||
// Assert - poll for message with timeout
|
||||
var foundMessage = WaitForMessage(MessageClass.ErrorMsg, timeoutMs: 1000);
|
||||
Assert.That(foundMessage, Is.Not.Null, "Expected an error message to be added");
|
||||
Assert.That(foundMessage.Text, Is.EqualTo(Language.ConnectionOpenFailedNoHostname));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OpenConnection_WithNullHostname_AddsErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var connectionInfo = new ConnectionInfo
|
||||
{
|
||||
Name = "Test Connection",
|
||||
Hostname = null, // Null hostname
|
||||
Protocol = ProtocolType.SSH2 // SSH doesn't support blank hostname
|
||||
};
|
||||
|
||||
// Act
|
||||
_connectionInitiator.OpenConnection(connectionInfo);
|
||||
|
||||
// Assert - poll for message with timeout
|
||||
var foundMessage = WaitForMessage(MessageClass.ErrorMsg, timeoutMs: 1000);
|
||||
Assert.That(foundMessage, Is.Not.Null, "Expected an error message to be added");
|
||||
Assert.That(foundMessage.Text, Is.EqualTo(Language.ConnectionOpenFailedNoHostname));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OpenConnection_WithValidHostname_DoesNotAddHostnameError()
|
||||
{
|
||||
// Arrange
|
||||
var connectionInfo = new ConnectionInfo
|
||||
{
|
||||
Name = "Test Connection",
|
||||
Hostname = "192.168.1.1", // Valid hostname
|
||||
Protocol = ProtocolType.RDP
|
||||
};
|
||||
|
||||
// Act
|
||||
_connectionInitiator.OpenConnection(connectionInfo);
|
||||
|
||||
// Give a moment for any potential async operations
|
||||
System.Threading.Thread.Sleep(200);
|
||||
|
||||
// Assert
|
||||
var hostnameErrors = _messageCollector.Messages
|
||||
.Where(m => m.Text == Language.ConnectionOpenFailedNoHostname)
|
||||
.ToList();
|
||||
|
||||
Assert.That(hostnameErrors, Is.Empty,
|
||||
"Should not have hostname error when hostname is provided");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls the message collector for a message of the specified class
|
||||
/// </summary>
|
||||
private IMessage WaitForMessage(MessageClass messageClass, int timeoutMs = 1000)
|
||||
{
|
||||
var startTime = DateTime.Now;
|
||||
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
|
||||
{
|
||||
var message = _messageCollector.Messages
|
||||
.FirstOrDefault(m => m.Class == messageClass);
|
||||
|
||||
if (message != null)
|
||||
return message;
|
||||
|
||||
System.Threading.Thread.Sleep(50); // Poll every 50ms
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
204
mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
Normal file
204
mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Connection.Protocol.AnyDesk;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace mRemoteNGTests.Connection.Protocol;
|
||||
|
||||
public class ProtocolAnyDeskTests
|
||||
{
|
||||
private ProtocolAnyDesk _protocolAnyDesk;
|
||||
private ConnectionInfo _connectionInfo;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_connectionInfo = new ConnectionInfo();
|
||||
_protocolAnyDesk = new ProtocolAnyDesk(_connectionInfo);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
_protocolAnyDesk?.Close();
|
||||
_protocolAnyDesk = null;
|
||||
_connectionInfo = null;
|
||||
}
|
||||
|
||||
#region IsValidAnydeskId Tests
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_NumericId_ReturnsTrue()
|
||||
{
|
||||
// Valid numeric AnyDesk ID
|
||||
bool result = InvokeIsValidAnydeskId("123456789");
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_AlphanumericWithAt_ReturnsTrue()
|
||||
{
|
||||
// Valid alias format: alias@ad
|
||||
bool result = InvokeIsValidAnydeskId("myalias@ad");
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithHyphen_ReturnsTrue()
|
||||
{
|
||||
// Valid ID with hyphen
|
||||
bool result = InvokeIsValidAnydeskId("my-alias@ad");
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithUnderscore_ReturnsTrue()
|
||||
{
|
||||
// Valid ID with underscore
|
||||
bool result = InvokeIsValidAnydeskId("my_alias@ad");
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithDot_ReturnsTrue()
|
||||
{
|
||||
// Valid ID with dot
|
||||
bool result = InvokeIsValidAnydeskId("alias.name@ad");
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithSemicolon_ReturnsFalse()
|
||||
{
|
||||
// Command injection attempt with semicolon
|
||||
bool result = InvokeIsValidAnydeskId("123456789; calc.exe");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithAmpersand_ReturnsFalse()
|
||||
{
|
||||
// Command injection attempt with ampersand
|
||||
bool result = InvokeIsValidAnydeskId("123456789 & calc.exe");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithPipe_ReturnsFalse()
|
||||
{
|
||||
// Command injection attempt with pipe
|
||||
bool result = InvokeIsValidAnydeskId("123456789 | calc.exe");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithRedirection_ReturnsFalse()
|
||||
{
|
||||
// Command injection attempt with redirection
|
||||
bool result = InvokeIsValidAnydeskId("123456789 > output.txt");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithBacktick_ReturnsFalse()
|
||||
{
|
||||
// PowerShell escape character
|
||||
bool result = InvokeIsValidAnydeskId("123456789`calc");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithDollarSign_ReturnsFalse()
|
||||
{
|
||||
// PowerShell variable indicator
|
||||
bool result = InvokeIsValidAnydeskId("123456789$var");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithParentheses_ReturnsFalse()
|
||||
{
|
||||
// Command substitution
|
||||
bool result = InvokeIsValidAnydeskId("123456789(calc)");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithNewline_ReturnsFalse()
|
||||
{
|
||||
// Newline injection
|
||||
bool result = InvokeIsValidAnydeskId("123456789\ncalc");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithCarriageReturn_ReturnsFalse()
|
||||
{
|
||||
// Carriage return injection
|
||||
bool result = InvokeIsValidAnydeskId("123456789\rcalc");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithQuotes_ReturnsFalse()
|
||||
{
|
||||
// Quote escape attempt
|
||||
bool result = InvokeIsValidAnydeskId("123456789\"calc\"");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_WithSingleQuotes_ReturnsFalse()
|
||||
{
|
||||
// Single quote escape attempt
|
||||
bool result = InvokeIsValidAnydeskId("123456789'calc'");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_EmptyString_ReturnsFalse()
|
||||
{
|
||||
// Empty string
|
||||
bool result = InvokeIsValidAnydeskId("");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_Whitespace_ReturnsFalse()
|
||||
{
|
||||
// Only whitespace
|
||||
bool result = InvokeIsValidAnydeskId(" ");
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsValidAnydeskId_Null_ReturnsFalse()
|
||||
{
|
||||
// Null string
|
||||
bool result = InvokeIsValidAnydeskId(null);
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Uses reflection to invoke the private IsValidAnydeskId method
|
||||
/// </summary>
|
||||
private bool InvokeIsValidAnydeskId(string anydeskId)
|
||||
{
|
||||
var method = typeof(ProtocolAnyDesk).GetMethod("IsValidAnydeskId",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
throw new Exception("IsValidAnydeskId method not found. The method may have been renamed or removed.");
|
||||
}
|
||||
|
||||
return (bool)method.Invoke(_protocolAnydesk, new object[] { anydeskId });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
208
mRemoteNGTests/Installer/InstalledWindowsUpdateCheckerTests.cs
Normal file
208
mRemoteNGTests/Installer/InstalledWindowsUpdateCheckerTests.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace mRemoteNGTests.Installer
|
||||
{
|
||||
[TestFixture]
|
||||
public class InstalledWindowsUpdateCheckerTests
|
||||
{
|
||||
private CustomActions.InstalledWindowsUpdateChecker _checker;
|
||||
private MethodInfo _sanitizeKbIdMethod;
|
||||
private MethodInfo _buildWhereClauseMethod;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_checker = new CustomActions.InstalledWindowsUpdateChecker();
|
||||
|
||||
// Use reflection to access private methods for testing
|
||||
var type = typeof(CustomActions.InstalledWindowsUpdateChecker);
|
||||
_sanitizeKbIdMethod = type.GetMethod("SanitizeKbId", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
_buildWhereClauseMethod = type.GetMethod("BuildWhereClauseFromKbList", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
#region SanitizeKbId Tests
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_ValidKbWithPrefix_ReturnsUppercased()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB1234567");
|
||||
Assert.That(result, Is.EqualTo("KB1234567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_ValidKbLowercase_ReturnsUppercased()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("kb1234567");
|
||||
Assert.That(result, Is.EqualTo("KB1234567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_ValidKbMixedCase_ReturnsUppercased()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("Kb1234567");
|
||||
Assert.That(result, Is.EqualTo("KB1234567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_ValidNumberOnly_ReturnsWithKbPrefix()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("1234567");
|
||||
Assert.That(result, Is.EqualTo("KB1234567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WithWhitespace_ReturnsTrimmedAndUppercased()
|
||||
{
|
||||
var result = InvokeSanitizeKbId(" KB1234567 ");
|
||||
Assert.That(result, Is.EqualTo("KB1234567"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_SqlInjectionAttempt_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB1234' OR '1'='1");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WqlInjectionWithSemicolon_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB1234; DROP TABLE");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WithSpecialCharacters_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB1234@#$");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_NullInput_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId(null);
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_EmptyString_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WhitespaceOnly_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId(" ");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_OnlyKbPrefix_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WithDashes_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB-1234567");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SanitizeKbId_WithUnderscores_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeSanitizeKbId("KB_1234567");
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BuildWhereClauseFromKbList Tests
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_SingleValidKb_ReturnsCorrectClause()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "KB1234567" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_MultipleValidKbs_ReturnsOrClause()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "KB1234567", "KB7654321" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_InvalidKb_SkipsInvalid()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "KB1234567", "KB1234'; DROP--", "KB7654321" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_AllInvalidKbs_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "'; DROP TABLE", "OR 1=1--" });
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_EmptyList_ReturnsEmpty()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(Array.Empty<string>());
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_NullValues_SkipsNulls()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "KB1234567", null, "KB7654321" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_MixedCaseKbs_NormalizesToUppercase()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "kb1234567", "KB7654321" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_DigitOnlyKb_AddsKbPrefix()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "1234567" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567'"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildWhereClause_MixedPrefixedAndDigitOnly_NormalizesAll()
|
||||
{
|
||||
var result = InvokeBuildWhereClause(new[] { "1234567", "KB7654321", "kb9999999" });
|
||||
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321' OR HotFixID='KB9999999'"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private string InvokeSanitizeKbId(string input)
|
||||
{
|
||||
return (string)_sanitizeKbIdMethod.Invoke(_checker, new object[] { input });
|
||||
}
|
||||
|
||||
private string InvokeBuildWhereClause(string[] kbList)
|
||||
{
|
||||
return (string)_buildWhereClauseMethod.Invoke(_checker, new object[] { kbList });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\mRemoteNG\mRemoteNG.csproj" />
|
||||
<ProjectReference Include="..\mRemoteNGInstaller\CustomActions\CustomActions.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
|
||||
Reference in New Issue
Block a user