264 Commits

Author SHA1 Message Date
Gary Sharp d1aee95d35 qol: user photos to honour image orientation metadata 2026-03-09 18:59:20 +11:00
Gary Sharp 72a709c11d qol: Saved Exports: informational message 2026-03-05 15:36:18 +11:00
Gary Sharp 4f7f6db804 qol: option to view/print generated document inline 2026-03-01 14:15:27 +11:00
Gary Sharp 892299a791 bug: device enrolment match initially match on account name instead of account id 2026-02-25 15:35:02 +11:00
Gary Sharp 48512fa9d1 qol: offline domain join to reuse AD computer accounts
Replaces old behaviour of deleting and creating new accounts. Now when a device has a new name, its existing account is renamed and reused.
2026-02-25 14:34:34 +11:00
Gary Sharp 204d57a4a5 feature: display pdf attachments inline 2026-01-26 16:34:26 +11:00
Gary Sharp f807d75162 #180: bulk download device/job/user attachments 2026-01-26 15:04:04 +11:00
Gary Sharp e809c63e37 #184: optionally set managed group descriptions 2026-01-26 12:31:01 +11:00
Gary Sharp e1f1973520 feature: Bootstrapper secure server discovery 2026-01-22 15:26:23 +11:00
Gary Sharp 71fa53bfb2 bug: on the home page a white bar appeared at the bottom of the screen when there was a pending enrolment 2026-01-22 15:22:23 +11:00
Gary Sharp 834d2f8fae feature: #UserDetails[] expression helper ignores hide/obfuscate mode symbols 2026-01-22 15:20:23 +11:00
Gary Sharp 2ab765f2d7 bug: edge case where the assigned user is incorrectly set for login 2026-01-22 15:17:19 +11:00
Gary Sharp 04be92a1df feature: online session authentication and one-way decryption 2026-01-01 19:21:00 +11:00
Gary Sharp 4e7c7c117b fix activation registration after local-network-access policy introduction 2025-12-29 13:14:52 +11:00
Gary Sharp f975c55b8a bug: whitespace missing in device/user flag tooltips 2025-12-10 14:38:58 +11:00
Gary Sharp 9e0d832aa1 bug fix: save non-warranty purchase order reference 2025-12-04 14:48:34 +11:00
Gary Sharp 94a31282eb bug fix #185: fix document package generation 2025-12-03 15:49:10 +11:00
Gary Sharp 529bba5c72 resolves #179: filter noticeboard by job queue 2025-10-31 17:45:04 +11:00
Gary Sharp 8424a9a9a2 feature #180: bulk download document attachment instances 2025-10-31 14:58:54 +11:00
Gary Sharp 202bbb163b qol: lazy-loading of device profile organisation units 2025-10-31 13:58:59 +11:00
Gary Sharp 0e15d2a880 fix: device batch timeline link 2025-10-19 19:19:28 +11:00
Gary Sharp 89c14084f5 fix #181 - explicitly fetch DeviceComments when showing a device 2025-10-19 19:11:00 +11:00
Gary Sharp e2c07f019c v2.5 release 2025-09-28 11:24:29 +10:00
Gary Sharp 7603cac01a feature: scheduled flag removal 2025-09-19 12:18:45 +10:00
Gary Sharp 356762c811 feature: enrol devices bound to another domain 2025-09-18 18:27:29 +10:00
Gary Sharp deaac0f073 bug fixes 2025-09-11 15:57:16 +10:00
Gary Sharp de10f92c02 minor bug fixes 2025-09-10 15:23:06 +10:00
Gary Sharp 3e514fdf9a feature: device profiles - set assigned user for logon 2025-09-07 12:48:10 +10:00
Gary Sharp 36c24542e1 update: minor changes to document template bulk generation 2025-09-07 12:06:23 +10:00
Gary Sharp ca7193a8fc feature: device document template bulk generation 2025-08-17 18:56:56 +10:00
Gary Sharp 676ff82e4b qol: user device assignment helper methods for expressions 2025-08-13 16:15:53 +10:00
Gary Sharp 53fdea5325 update dependencies: jQuery, jQuery UI, jQuery Validate, jQuery Validate Unobtrusive, SignalR 2025-08-09 19:00:29 +10:00
Gary Sharp 0c0a7bf297 use fetch over $.getJSON 2025-08-09 18:43:44 +10:00
Gary Sharp 57aeaa3eaa remove disconnected dialogs 2025-08-09 16:17:10 +10:00
Gary Sharp d291ad464f qol: online upload: remove dialog when attachment received 2025-08-01 11:40:02 +10:00
Gary Sharp be7ee4cae8 feature: flag permissions
feature: flag permissions
2025-07-31 19:03:33 +10:00
Gary Sharp 7deead494b security: use more antiforgery tokens 2025-07-31 16:18:32 +10:00
Gary Sharp fd43d85778 qol: simplify and Tuple.Create() 2025-07-20 15:58:42 +10:00
Gary Sharp 4c27b0ff3c qol: use type keywords and more interpolated strings 2025-07-20 15:44:44 +10:00
Gary Sharp 58546ed16d qol: simplify default 2025-07-20 15:31:35 +10:00
Gary Sharp 9656c15c4b qol: simplify calls 2025-07-20 15:20:22 +10:00
Gary Sharp 1add4ee0f5 qol: inline variable declaration 2025-07-20 15:12:33 +10:00
Gary Sharp 5792771ea1 qol: formatting 2025-07-20 13:47:56 +10:00
Gary Sharp 49da389c2c qol: unnecessary usings 2025-07-20 12:00:57 +10:00
Gary Sharp 7faebe56a8 qol: interpolated strings 2025-07-20 11:59:28 +10:00
Gary Sharp 4e518d6684 qol: remove this 2025-07-20 11:39:42 +10:00
Gary Sharp b4e54c9edf qol: simplify accessors 2025-07-20 11:24:04 +10:00
Gary Sharp 3aeb9374a9 feature: job export supports individual user details 2025-07-17 13:09:10 +10:00
Gary Sharp 0905a02aea #172 attempt reconnect if signalR disconnects 2025-07-17 12:44:06 +10:00
Gary Sharp f8fd1a58a3 #145 add comments for devices 2025-07-17 11:40:50 +10:00
Gary Sharp 2184c9e22e add comments for users [#145] 2025-07-12 19:55:58 +10:00
Gary Sharp 42e9045d5e bug fixed: users with Remove Own Attachment permission had to refresh before they could remove 2025-07-12 19:51:54 +10:00
Gary Sharp 092c6249ee refactor job logs/comments [#145] 2025-07-12 16:48:27 +10:00
Gary Sharp b56e82d5c4 resolves #151: BYOD sub type added to User - Management jobs 2025-07-11 13:25:45 +10:00
Gary Sharp 54a578f4a1 feature: custom device models 2025-07-11 12:55:50 +10:00
Gary Sharp 3f1fa3d7de Resolves #173: Feature Request: Filter box for device/user flag assignment 2025-07-05 17:01:19 +10:00
Gary Sharp caa0bedc93 Update #9: by default, hide decommissioned devices from search 2025-07-05 14:14:07 +10:00
Gary Sharp 6e99b4503b feature: device model/profile decommissioning 2025-07-05 13:55:44 +10:00
Gary Sharp 583552ffdd feature: batch device decommissioning 2025-07-03 19:13:52 +10:00
Gary Sharp 4660425ccc qol: quick search input to have fixed width 2025-07-03 12:53:47 +10:00
Gary Sharp 8b8b26116e feature: specify device profile/batch during pending enrolment 2025-06-27 14:51:03 +10:00
Gary Sharp 57ae665070 bug: remove stray braces from the Device Batch and Profile views 2025-06-18 13:18:57 +10:00
Gary Sharp f06a37ebb4 bug: remove stray brace from device model view 2025-06-18 13:09:31 +10:00
Gary Sharp ee273ffe03 bug: show correct error message if Online Upload session fails 2025-06-18 13:03:47 +10:00
Gary Sharp 0672bdb7c0 qol: avoid license/activation confusion by renaming license Activate button to Apply 2025-06-18 13:03:20 +10:00
Gary Sharp 662e1ed231 feature: Upload Online Attachments front end 2025-06-15 18:01:01 +10:00
Gary Sharp cc1f224456 feature: Upload Online Attachments 2025-06-15 17:56:13 +10:00
Gary Sharp 6e2c36d4ae bug: enforce device details scope 2025-06-15 17:51:35 +10:00
Gary Sharp bccbb20b84 bug fix #171 device batch attachments not working 2025-03-14 16:40:55 +11:00
Gary Sharp fd3eb80648 bug fix: online services authentication token not expiring 2025-03-10 14:35:57 +11:00
Gary Sharp 9a171a8417 bug fix #168 device export: batch filtering inverted 2025-03-04 17:34:51 +11:00
Gary Sharp eb1ffae212 bug fix: export user details without User.ShowDetails claim 2025-02-27 13:54:43 +11:00
Gary Sharp d31c2db43b regression fix: device page header overflow 2025-02-27 13:17:04 +11:00
Gary Sharp 62a5da7513 regression fix: quick search 2025-02-27 13:16:37 +11:00
Gary Sharp eb3ed7f819 regression fix: ajax icons not appearing 2025-02-26 17:59:08 +11:00
Gary Sharp a9687b5f25 feature: user details are individually exported; shared export field renderer 2025-02-26 13:23:24 +11:00
Gary Sharp 539503133a qol: fix System.Net.Http dependencies 2025-02-26 13:16:29 +11:00
Gary Sharp d2b5e7ab6a exporting: remember last options for documents/user flags/device flags 2025-02-26 13:06:34 +11:00
Gary Sharp 04430bba50 bug fix #162 document template id closure not updated on subsequent invoke 2025-02-17 18:08:22 +11:00
Gary Sharp 162ca20046 fix: enrollment > enrolment 2025-02-16 17:13:55 +11:00
Gary Sharp 786d4809b5 feature: document instance exporting
resolves #154
2025-02-14 16:05:23 +11:00
Gary Sharp d3cef11796 feature: exports api refactoring 2025-02-14 13:07:30 +11:00
Gary Sharp 2c215e4856 feature: saved exports; include jobs and flags 2025-02-13 18:20:47 +11:00
Gary Sharp eec8ba438c feature: saved exports view/edit/trigger 2025-02-12 18:36:30 +11:00
Gary Sharp 59dc0b9d5a regression bug: csv export was failing 2025-02-12 15:39:18 +11:00
Gary Sharp ac24055365 feature: saved exports
initial - not feature complete
2025-02-09 17:14:04 +11:00
Gary Sharp 2fce645066 refactor: simplify export metadata construction 2025-02-07 16:12:19 +11:00
Gary Sharp 67f1c2a5d1 refactor: make exporting consistent 2025-02-06 19:14:36 +11:00
Gary Sharp f946f3250c feature: collect MDM Hardware Data (Autopilot hash) during device enrolment
resolves #160
2025-02-05 17:38:57 +11:00
Gary Sharp 0853bcee50 feature: initial comments template
completes #150
2025-01-24 17:28:27 +11:00
Gary Sharp 408e1c4c14 qol: make Online Services Connect more reliable 2025-01-23 15:14:30 +11:00
Gary Sharp dee54bb6d7 feature: choose which noticeboard components to display with the link builder
also restyle noticeboard using flex
2025-01-22 17:58:45 +11:00
Gary Sharp 50603210b0 update expression browser route 2025-01-19 21:44:50 +11:00
Gary Sharp b640e51874 feature: add clipboard link to various identifiers (resolves #148) 2025-01-18 13:33:55 +11:00
Gary Sharp 7a336e699a request: device profile OUs should be sorted alphabetically 2025-01-17 13:05:06 +11:00
Gary Sharp a3e1e1d030 feature: update expression browser and add plugin expression extensions 2025-01-16 12:36:01 +11:00
Gary Sharp 963970feeb update list of shipped binaries for plugins 2025-01-15 15:36:44 +11:00
Gary Sharp 296f7a13fd feature: online services push notifications 2025-01-01 19:23:18 +11:00
Gary Sharp 4006bdbcc6 bug: AD ignore foreign security principals 2024-12-30 14:25:08 +11:00
Gary Sharp 26bac92e5a qol: online services authentication expiration offset 2024-12-30 14:24:28 +11:00
Gary Sharp b6dfaa3445 feature: online activation 2024-12-28 08:12:31 +11:00
Gary Sharp 39ba206831 qol: attachments newest to oldest and adjustments so attachments are shown 2024-12-18 20:09:10 +11:00
Gary Sharp 7550e0e45d qol: wait for attachment thumbnail generation and show additional file type-specific icons 2024-12-18 19:37:15 +11:00
Gary Sharp 9f10eeeb70 qol: make attachment comments optional 2024-12-18 18:40:23 +11:00
Gary Sharp 77acf0bfdb bug fix #104 hardware mac addresses not saving on MacOSX enrol 2024-12-15 18:00:40 +11:00
Gary Sharp f0dfe45cd4 bug fix #153 save job log message when bulk generating document templates 2024-12-15 17:42:32 +11:00
Gary Sharp fb6432a5c8 feature: add email address to organisation address (resolves #156) 2024-12-15 17:08:49 +11:00
Gary Sharp b15917254f clean up: remove unused namespaces 2024-12-14 16:55:37 +11:00
Gary Sharp 8abe31f430 qol: use unified exporting for logs 2024-12-14 16:49:31 +11:00
Gary Sharp a6b9cd1af2 feature: job exporting (resolves #155) 2024-12-12 16:02:12 +11:00
Gary Sharp 90c709c4c1 feature: #158 adds job preference option to include all attachments on lodgment 2024-12-11 17:01:42 +11:00
Gary Sharp 25e3a8e1f1 feature: add select all/none attachments when submitting waranty/repair/insurance job 2024-12-11 15:15:22 +11:00
Gary Sharp 343f20980d qol: impove plugin installation/update api 2024-12-11 15:13:26 +11:00
Gary Sharp f36871abe2 feature: dont disable a computer account if a duplicate, commissioned device exists 2024-12-11 15:11:33 +11:00
Gary Sharp cb354cd13e skip disabling computer account during decommissioning if another active device with the same name exists 2024-10-10 16:17:38 +11:00
Gary Sharp 09f9f2d427 version bump for v2.4 release 2024-09-26 16:33:55 +10:00
Gary Sharp 390857e065 attempt to preserve IIS bindings so future installations can restore them 2024-09-25 18:16:32 +10:00
Gary Sharp e0d620bf67 feature: lodge insurance 2024-09-25 16:21:51 +10:00
Gary Sharp 78b7b059ea feature: expression helpers - basic email sending 2024-05-16 22:11:10 +10:00
Gary Sharp bb846d14c5 feature: job preferences - On Device Ready For Return expression 2024-05-16 22:09:42 +10:00
Gary Sharp 565e1707ce fix: importing new devices fails for domain joined machines with assigned users 2024-04-08 18:11:03 +10:00
Gary Sharp a675e4a6e9 Feature: Provide CurrentDeviceUserAssignments for use by document expressions 2024-03-28 11:30:39 +11:00
Gary Sharp d8eb8fec83 global support license and validation 2024-01-26 16:23:26 +11:00
Gary Sharp f90eda4101 add pending enrollment identifier 2024-01-24 16:38:50 +11:00
Gary Sharp 37e2e5a08c feature: pending device enrollment approval 2024-01-21 14:06:11 +11:00
Gary Sharp 6af83cbdb2 Merge branch 'device-flags' 2024-01-14 19:13:51 +11:00
Gary Sharp 5f4cb20900 #138 display device flags where appropriate in the UI 2024-01-14 19:13:09 +11:00
Gary Sharp aee467cb53 #138 add/remove individual device flags UI 2024-01-14 18:29:58 +11:00
Gary Sharp 6ce6e2cccf #138 device flags configuration API and UI 2024-01-14 17:59:30 +11:00
Gary Sharp cd858c2215 #138 device flag searching 2024-01-14 17:56:20 +11:00
Gary Sharp 7e07c07171 #138 device flag export service 2024-01-14 17:55:20 +11:00
Gary Sharp 15e2806731 #138 device flag services 2024-01-14 17:54:06 +11:00
Gary Sharp 53baf4eb78 #138 device flag authorization claims 2024-01-13 16:56:23 +11:00
Gary Sharp 8c48ab6ecd #138 device flag models 2024-01-13 16:33:03 +11:00
Gary Sharp 8afe4195a9 #107 allow moving of device OU when changing profile 2024-01-12 12:53:50 +11:00
Gary Sharp dcc4fcb984 #107 support moving profiles devices when changing the organisational unit 2024-01-12 12:52:39 +11:00
Gary Sharp 4631903019 refactor: split windows/mac enrolment 2024-01-12 11:13:48 +11:00
Gary Sharp 85d51c0e45 Fix #141 preserve computer names for non-domain joined devices 2024-01-11 16:27:40 +11:00
Gary Sharp 974a07f3bb show a warning when a device profile OU couuld not be found 2023-11-24 12:58:12 +11:00
Gary Sharp b576aec641 feature: computer name template testing 2023-11-24 11:57:26 +11:00
Gary Sharp a2aaa4c913 #140 make additional fontawesome icons available for user flags 2023-11-12 21:25:16 +11:00
Gary Sharp 318a70d9a3 #140 upgrade fontawesome 2023-11-12 21:24:16 +11:00
Gary Sharp 53e57d4017 feature: user flag assignment exporting 2023-11-11 20:04:52 +11:00
Gary Sharp 143dc1b3e6 bug fix: when exporting devices refresh details before extracting records instead of after 2023-11-11 18:59:59 +11:00
Gary Sharp a4f18d1d49 initial refactor of device export so it can be reused for future exporting 2023-11-11 17:52:24 +11:00
Gary Sharp 46222f2a78 hide decommissioned device profiles and batches by default when updating a devices batch or profile 2023-11-09 20:48:16 +11:00
Gary Sharp 2e091383ec fixes #134 - hide decommissioned device profiles by default 2023-11-09 20:05:25 +11:00
Gary Sharp 7ace4d1c7e add padding row to device batch list to maintain alternating row styles 2023-11-09 19:55:22 +11:00
Gary Sharp f901d45d78 support Firefox checkbox inside anchor element when logging warranty 2023-11-09 14:05:48 +11:00
Gary Sharp 028d1ccc64 remove localhost-only requirement for post upgrade maintenance 2023-11-09 13:43:51 +11:00
Gary Sharp 7b6a44921d plugin library: link to configuration instead of disabled "installed" button 2023-11-09 13:43:09 +11:00
Gary Sharp 23406e5e39 include more inner exception data in system logging 2023-11-08 16:51:08 +11:00
Gary Sharp 83557e6f0c include external job references in detailed search 2023-11-08 16:49:19 +11:00
Gary Sharp 1c90c89158 upgrade ssh.net 2023-11-06 11:25:38 +11:00
Gary Sharp 9924153e82 upgrade dotnet framework 4.5 to 4.6 2023-11-06 11:24:58 +11:00
Gary Sharp 3ec2ea7d37 add job log when re-opening 2023-11-03 15:50:01 +11:00
Gary Sharp b6945d9bbd fix #135 avoid failing device import/enrol when unable to set AD description 2023-10-12 17:37:42 +11:00
Gary Sharp 6740a7479a bug: disable image (camera) attachment upload unless over HTTPS
Browser restriction: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#privacy_and_security
2023-10-12 16:34:35 +11:00
Gary Sharp a3fb09440d feature: document template QR Code image extensions 2023-10-12 16:07:14 +11:00
Gary Sharp 7353405b16 use native media devices to capture webcam images 2023-10-08 16:25:32 +11:00
Gary Sharp 6fcb1a1eae fixup Disco -> Disco ICT 2023-07-26 19:01:31 +10:00
Gary Sharp e204ea4d5f version bump for release candidate 2023-06-10 17:18:04 +10:00
Gary Sharp 0a4a2816a0 feature: bulk generate documents for device batches, models and profiles 2023-05-04 15:30:04 +10:00
Gary Sharp 473b02f718 bug fix: TryBinaryNumericEncode on small numbers 2023-05-04 15:27:46 +10:00
Gary Sharp 5a2c29ea0b bug: unable to bulk generate documents for devices/job; honor the id sort order 2023-05-04 13:06:04 +10:00
Gary Sharp 1a8ec32f0f view batch devices from index
requested by @RB
2023-04-28 15:39:44 +10:00
Gary Sharp 9e7daf2043 data tables remember pagination size preference 2023-04-28 14:36:01 +10:00
Gary Sharp 0c690cff5a bulk generation refactoring 2023-04-27 14:54:32 +10:00
Gary Sharp b55b77de9f initialize plugin scheduled tasks when installed 2023-04-26 18:16:46 +10:00
Gary Sharp 937508c440 document template user flag rules should use the creator user for flag assignment 2023-04-16 15:14:14 +10:00
Gary Sharp cfe4c4b912 refactor expression caching; add document fields 2023-04-16 14:02:35 +10:00
Gary Sharp d75663a219 document template on import user flag rules 2023-04-16 09:50:19 +10:00
Gary Sharp 1e24844e88 respect whitespace when rendering logs 2023-04-14 16:40:30 +10:00
Gary Sharp 273b67c422 support user details category in document bulk generation 2023-04-14 16:40:13 +10:00
Gary Sharp 13549e7ec4 bug fix: calculate task progress offsets and multiplier correctly 2023-04-14 16:39:14 +10:00
Gary Sharp bbb40bd5c4 register plugin references before initialization 2023-04-14 16:38:38 +10:00
Gary Sharp 22dad072b9 refactor user details plugin interface 2023-04-14 16:37:42 +10:00
Gary Sharp 215e9863a2 bug: client was unable to run djoin.exe
perhaps a behaviour change between dotnet 4.5 and 4.5.2. It was looking for djoin.exe in the SysWow directory.
2023-02-12 18:42:19 +11:00
Gary Sharp 190abc72dd bug: when adding computer offline, computer name should not be required 2023-02-09 20:03:20 +11:00
Gary Sharp af75a55d44 AddFlag helper method for expressions 2023-02-08 18:03:55 +11:00
Gary Sharp e05206d405 bug: correctly store email settings 2023-02-08 16:57:49 +11:00
Gary Sharp 53eed02a4f globally enable TLS1.2 2023-02-08 16:52:28 +11:00
Gary Sharp 57a7f67c3a remove device custom details
this plugin functionality has never been used
2023-02-08 16:24:21 +11:00
Gary Sharp 47e11c0fd6 bugs: minor 2023-01-29 10:10:37 +11:00
Gary Sharp 708ca16de8 minor framework updates 2023-01-29 10:07:19 +11:00
Gary Sharp 535a3f2967 bug: dont throw if unable to resolve online services 2023-01-28 13:37:27 +11:00
Gary Sharp ebd1c79f64 fix: increase pdf generated image resolution 2022-12-15 16:23:54 +11:00
Gary Sharp 7194dd612d bug: image montage layout 2022-12-15 16:08:24 +11:00
Gary Sharp 584bc07824 minor: when creating a job scoped document template default to all job types 2022-12-15 15:00:31 +11:00
Gary Sharp 2bd9238090 feature: support hiding user details with asterisk and ampersand suffixes 2022-12-09 12:23:41 +11:00
Gary Sharp b11fa5b1d6 Newtonsoft.Json 13.0.2 update 2022-12-08 11:15:34 +11:00
Gary Sharp 7667eaa49e maintenance: improved css minify 2022-12-04 15:21:04 +11:00
Gary Sharp 9f7c0f965e Microsoft.Owin update 2022-12-04 15:12:33 +11:00
Gary Sharp a274a33cf6 Newtonsoft.Json update 2022-12-04 15:05:51 +11:00
Gary Sharp 4acfd54319 Merge pull request #119 from garysharp/dependabot/nuget/Disco.Web.Extensions/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Web.Extensions
2022-12-04 14:20:40 +11:00
dependabot[bot] 941f1cf13e Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Web.Extensions
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 03:20:30 +00:00
Gary Sharp f225edd2d4 Merge pull request #118 from garysharp/dependabot/nuget/Disco.Data/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Data
2022-12-04 14:20:06 +11:00
Gary Sharp 5824680e2a Merge pull request #117 from garysharp/dependabot/nuget/Disco.Models/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Models
2022-12-04 14:19:55 +11:00
Gary Sharp ed4ec890da Merge pull request #120 from garysharp/dependabot/nuget/Disco.Client/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Client
2022-12-04 14:19:39 +11:00
Gary Sharp 56453fa081 Merge pull request #121 from garysharp/dependabot/nuget/Disco.Services/Newtonsoft.Json-13.0.1
Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Services
2022-12-04 14:19:25 +11:00
dependabot[bot] 11c824518f Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Client
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 03:18:33 +00:00
dependabot[bot] d720303a83 Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Data
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 03:18:32 +00:00
dependabot[bot] 02b29948ce Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Models
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 03:18:29 +00:00
dependabot[bot] a152365312 Bump Newtonsoft.Json from 6.0.3 to 13.0.1 in /Disco.Services
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 6.0.3 to 13.0.1.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/6.0.3...13.0.1)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 03:18:22 +00:00
Gary Sharp dba6c383f6 moment.js update 2022-12-04 14:17:31 +11:00
dependabot[bot] ba32b3b717 Bump Moment.js from 2.7.0 to 2.29.4 in /Disco.Web
Bumps [Moment.js](https://github.com/timrwood/moment) from 2.7.0 to 2.29.4.
- [Release notes](https://github.com/timrwood/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/timrwood/moment/compare/2.7.0...2.29.4)

---
updated-dependencies:
- dependency-name: Moment.js
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 14:00:00 +11:00
Gary Sharp 51cebd0706 maintenance: update T4MVC 2022-12-04 14:00:00 +11:00
Gary Sharp 983bdbefb9 maintenance: rebuild bundling/minify 2022-12-04 13:59:59 +11:00
Gary Sharp be99083d6a feature: bulk generating user documents 2022-12-04 13:47:24 +11:00
Gary Sharp 4c19a1ef63 minor: return correct content type for jpg 2022-12-04 13:47:24 +11:00
Gary Sharp 4f9f0fd0a8 maintenance: plugin refactoring 2022-12-04 13:47:24 +11:00
Gary Sharp 05593a1466 maintenance: document template refactoring 2022-12-04 13:47:23 +11:00
Gary Sharp 2873b4be8a bug: log detected thumbnail after collecting more information 2022-12-04 13:47:23 +11:00
Gary Sharp 7a1ff211a0 feature: user contact details 2022-12-04 13:47:23 +11:00
Gary Sharp fcf70f724e maintenance: dependency updates 2022-12-04 13:47:22 +11:00
Gary Sharp 99be87ed9c maintenance: AD integration refactoring 2022-12-04 13:47:22 +11:00
Gary Sharp 261baf669e fix: add accept attribute to file pickers 2022-12-04 13:47:22 +11:00
Gary Sharp b0fddd491b fix: simplify document template managed groups 2022-12-04 13:47:21 +11:00
Gary Sharp dff47c0e8b feature: export device custom details 2022-12-04 13:47:21 +11:00
Gary Sharp ed58619919 fix: configuration optimization 2022-12-04 13:47:20 +11:00
Gary Sharp 2a2731b9f3 feature: task-based document bulk generation 2022-12-04 13:47:20 +11:00
Gary Sharp 13e666d95a feature: document handlers 2022-12-04 13:47:19 +11:00
Gary Sharp 96cccab958 feature: pdf field locations cached 2022-12-04 13:47:18 +11:00
Gary Sharp e463a7361e feature: email address and phone numbers as links 2022-12-04 13:47:17 +11:00
Gary Sharp 58e467b6d6 feature: support importing device computer name 2022-12-04 13:47:17 +11:00
Gary Sharp 81bd6ccd83 bug: importing a PDF attachment fails if the template or target no longer exist 2022-09-07 18:08:28 +10:00
Gary Sharp 3e7db6b552 qol: remove exceptionless 2022-02-18 13:57:42 +11:00
Gary Sharp 09a6369693 qol: update javascript bundling 2022-02-18 13:52:31 +11:00
Gary Sharp 9bfeff8c42 feature: expose custom details to expressions
custom details can now be easily retrieved from any expression
2021-02-07 18:17:03 +11:00
Gary Sharp 3e57af394d feature: custom details first-class
custom details (such as those from the UserDetails plugin) can now be more deeply integrated throughtout the system
2021-02-07 18:15:52 +11:00
Gary Sharp e11d0871c4 feature: add reply-to address for email service 2021-02-05 15:24:20 +11:00
Gary Sharp f7fdfb0c8a maintenance unify document generation ui 2021-01-13 15:41:51 +11:00
Gary Sharp 806aadd161 feature: email service and configuration 2021-01-10 13:57:25 +11:00
Gary Sharp af4a94870e additional device details 2020-12-04 15:51:23 +11:00
Gary Sharp 486ce17857 Feature: Export device hardware audit information
Allows processor, memory, disk and network adapter information to be included in device exports
2020-11-29 16:46:20 +11:00
Gary Sharp f82b47dd46 Feature: Display detailed device hardware audit information
The device Details tab now displays processor, memory, disk and network adapter information collected at the previous enrolment
2020-11-29 16:45:10 +11:00
Gary Sharp f1d27732c7 Feature: Collect additional hardware audit information
When devices enrol the client collects processor, memory, disk and network information which is persisted in the database
2020-11-29 16:43:44 +11:00
Gary Sharp 28e5901929 Feature: Device Batch Attachments
allows for attachments to be uploaded and associated with Device Batches
2020-11-29 16:41:56 +11:00
Gary Sharp e531ffe2b7 support active directory trust relationships 2020-11-26 15:01:03 +11:00
Gary Sharp 4fca015afa bug fix: delete devices with assignment history 2020-11-13 20:15:31 +11:00
Gary Sharp a80ed14038 bug fix: csv device import first record was skipped 2020-09-10 12:43:03 +10:00
Gary Sharp f3e0939a06 Bug Fix: Ldap Query Escaping 2018-05-21 16:22:41 +10:00
Gary Sharp 2cd6ddfa24 Bug: EntityFramework does not understand Tuple.Create 2018-04-26 15:38:25 +10:00
Gary Sharp b4a30061e3 Bug fix #109 SQL Server 2016 Support 2018-04-23 18:21:44 +10:00
Gary Sharp cb033c0b70 Allow remote maintenance via web.config setting 2018-04-19 17:11:19 +10:00
Gary Sharp 3c197e7b2f Bug: Correctly write enrolment log device info 2018-04-19 17:09:34 +10:00
Gary Sharp 9f62da9ccd Bug: Stale cache when adding user flag in expression 2018-04-19 17:08:43 +10:00
Gary Sharp df721d7d6e Searching: Add detail to device search results 2018-04-19 17:06:58 +10:00
Gary Sharp 8278efb9a6 Bug: AD distinguishedName attribute name 2018-04-19 17:05:02 +10:00
Gary Sharp d477ad5d9c Client: Use UUID if no serial number is found 2018-04-19 17:04:25 +10:00
Gary Sharp 0755b6fc8a Workaround: Dont remove client certificates 2018-04-19 17:03:21 +10:00
Gary Sharp 78dd494304 Fix: Device import case-sensitive serial numbers 2018-04-19 17:02:38 +10:00
Gary Sharp 0497e145ea Bug fix: Allow for unexpected cert subject names 2017-05-25 13:47:17 +10:00
Gary Sharp 4d6aa18095 Perf: Save only when user updated 2017-03-29 13:00:15 +11:00
Gary Sharp fdf1bd4bc6 UI: Device models list, default hide decommissioned 2017-03-29 12:55:07 +11:00
Gary Sharp 82183f6508 UI: Device Table hide/show decommissioned devices 2017-03-29 12:18:37 +11:00
Gary Sharp d6ee70b860 Fix: NetBIOS computer name limit correction
16 character limit, not 24 as previously thought. Due to appended '$'
for machine accounts, a character limit of 15 is now enforced.
2017-03-29 12:17:01 +11:00
Gary Sharp 5ce9e51ae7 Feature: MS Excel (xlsx) Import/Export
Microsoft Excel files can be used to import/export devices. Several
import bugs were also fixed in the process.
2017-03-25 15:37:28 +11:00
Gary Sharp ed66f4f285 Tidy: Sort/remove usings, simplify names 2017-03-25 15:29:51 +11:00
Gary Sharp 526f8547f7 Client/Bootstrapper Fix: Try gathering model from BaseBoard 2017-03-24 15:50:18 +11:00
Gary Sharp d8e93cb31b Fix: Generate pdf for unimported users
If a user wasn't previously imported into the database generating of
PDFs failed. This change attempts to import the user if its not found.
2017-03-24 15:36:05 +11:00
Gary Sharp 5fa74a8cc0 Bug fix #106 - job queues: case-sensitive usernames 2016-12-08 17:01:13 +11:00
Gary Sharp a0d643eda1 Version bump for #103 2016-11-21 16:40:50 +11:00
Gary Sharp 0887eb447e Bug fix #103 Save User Flag assignment to DB 2016-11-21 16:02:45 +11:00
1337 changed files with 203907 additions and 86695 deletions
+4
View File
@@ -0,0 +1,4 @@
[*.cs]
# VSSpell001: Spell Check
dotnet_diagnostic.VSSpell001.severity = suggestion
@@ -8,8 +8,8 @@ using Disco.Services.Expressions;
using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Interop.ActiveDirectory;
using iTextSharp.text.pdf; using iTextSharp.text.pdf;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -17,76 +17,103 @@ namespace Disco.BI.Extensions
{ {
public static class DocumentTemplateExtensions public static class DocumentTemplateExtensions
{ {
internal const string CacheTemplate = "DocumentTemplate_{0}"; public static Tuple<Dictionary<string, Expression>, List<DocumentField>> CreateExpressions(string templateFileName, DiscoDataContext database)
{
Dictionary<string, Expression> expressions = new Dictionary<string, Expression>();
List<DocumentField> fields = new List<DocumentField>();
public static ConcurrentDictionary<string, Expression> PdfExpressionsFromCache(this DocumentTemplate dt, DiscoDataContext Database) PdfReader pdfReader = new PdfReader(templateFileName);
{
string cacheModuleKey = string.Format(CacheTemplate, dt.Id);
var module = ExpressionCache.GetModule(cacheModuleKey);
if (module == null)
{
// Cache
string templateFilename = dt.RepositoryFilename(Database);
PdfReader pdfReader = new PdfReader(templateFilename);
int pdfFieldOrdinal = 0; int pdfFieldOrdinal = 0;
foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys) foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys)
{ {
var pdfField = pdfReader.AcroFields.Fields[pdfFieldKey];
var pdfFieldPositions = pdfReader.AcroFields.GetFieldPositions(pdfFieldKey);
var pdfFieldFlags = pdfField.GetMerged(0).GetAsNumber(PdfName.FF)?.IntValue ?? 0;
var isRequired = (pdfFieldFlags & 2) == 2;
var isReadOnly = (pdfFieldFlags & 1) == 1;
var pdfFieldValue = pdfReader.AcroFields.GetField(pdfFieldKey); var pdfFieldValue = pdfReader.AcroFields.GetField(pdfFieldKey);
ExpressionCache.SetValue(cacheModuleKey, pdfFieldKey, Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal)); var pdfFieldPosition = default(RectangleF?);
if (pdfFieldPositions != null && pdfFieldPositions.Count > 0)
{
var position = pdfFieldPositions.First().position;
pdfFieldPosition = new RectangleF(position.Left, position.Top, position.Width, position.Height);
}
var fieldTypeId = pdfReader.AcroFields.GetFieldType(pdfFieldKey);
var fieldType = DocumentFieldType.None;
if (fieldTypeId <= 8 && fieldTypeId > 0)
fieldType = (DocumentFieldType)fieldTypeId;
var fixedValues = default(List<string>);
if (fieldType == DocumentFieldType.RadioButton || fieldType == DocumentFieldType.Checkbox)
{
fixedValues = pdfReader.AcroFields.GetAppearanceStates(pdfFieldKey).ToList();
}
expressions[pdfFieldKey] = Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, isRequired, isReadOnly, pdfFieldPosition);
fields.Add(new DocumentField(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, fieldType, isRequired, isReadOnly, fixedValues));
pdfFieldOrdinal++; pdfFieldOrdinal++;
} }
pdfReader.Close(); pdfReader.Close();
module = ExpressionCache.GetModule(cacheModuleKey, true);
} return Tuple.Create(expressions, fields);
return module;
} }
public static List<Expression> ExtractPdfExpressions(this DocumentTemplate dt, DiscoDataContext Database) private static Tuple<Dictionary<string, Expression>, List<DocumentField>> CreateExpressions(DocumentTemplate dt, DiscoDataContext database)
{ {
return dt.PdfExpressionsFromCache(Database).Values.OrderBy(e => e.Ordinal).ToList(); string templateFileName = dt.RepositoryFilename(database);
return CreateExpressions(templateFileName, database);
} }
public static Stream GeneratePdfBulk(this DocumentTemplate dt, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, params string[] DataObjectsIds) public static Dictionary<string, Expression> PdfExpressionsFromCache(this DocumentTemplate dt, DiscoDataContext database)
{ {
return Interop.Pdf.PdfGenerator.GenerateBulkFromTemplate(dt, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjectsIds); return ExpressionCache.GetOrCreateExpressions(dt, () => CreateExpressions(dt, database));
} }
public static Stream GeneratePdfBulk(this DocumentTemplate dt, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, params IAttachmentTarget[] DataObjects)
public static List<DocumentField> PdfFieldsFromCache(this DocumentTemplate dt, DiscoDataContext database)
{ {
return Interop.Pdf.PdfGenerator.GenerateBulkFromTemplate(dt, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjects); return ExpressionCache.GetOrCreateFields(dt, () => CreateExpressions(dt, database));
} }
public static Stream GeneratePdf(this DocumentTemplate dt, DiscoDataContext Database, IAttachmentTarget Target, User CreatorUser, DateTime TimeStamp, DocumentState State, bool FlattenFields = false)
public static List<Expression> ExtractPdfExpressions(this DocumentTemplate dt, DiscoDataContext database)
{
return dt.PdfExpressionsFromCache(database).Values.OrderBy(e => e.Ordinal).ToList();
}
public static Stream GeneratePdf(this DocumentTemplate dt, DiscoDataContext database, IAttachmentTarget target, User creatorUser, DateTime timeStamp, DocumentState state, bool flattenFields = false)
{ {
bool generateExpression = !string.IsNullOrEmpty(dt.OnGenerateExpression); bool generateExpression = !string.IsNullOrEmpty(dt.OnGenerateExpression);
string generateExpressionResult = null; string generateExpressionResult = null;
if (generateExpression) if (generateExpression)
generateExpressionResult = dt.EvaluateOnGenerateExpression(Target, Database, CreatorUser, TimeStamp, State); generateExpressionResult = dt.EvaluateOnGenerateExpression(target, database, creatorUser, timeStamp, state);
var pdfStream = Interop.Pdf.PdfGenerator.GenerateFromTemplate(dt, Database, Target, CreatorUser, TimeStamp, State, FlattenFields); var pdfStream = Interop.Pdf.PdfGenerator.GenerateFromTemplate(dt, database, target, creatorUser, timeStamp, state, flattenFields);
if (generateExpression) if (generateExpression)
DocumentsLog.LogDocumentGenerated(dt, Target, CreatorUser, generateExpressionResult); DocumentsLog.LogDocumentGenerated(dt, target, creatorUser, generateExpressionResult);
else else
DocumentsLog.LogDocumentGenerated(dt, Target, CreatorUser); DocumentsLog.LogDocumentGenerated(dt, target, creatorUser);
return pdfStream; return pdfStream;
} }
public static Stream GeneratePdfPackage(this DocumentTemplatePackage package, DiscoDataContext Database, IAttachmentTarget Target, User CreatorUser, DateTime TimeStamp, DocumentState State) public static Stream GeneratePdfPackage(this DocumentTemplatePackage package, DiscoDataContext database, IAttachmentTarget target, User creatorUser, DateTime timeStamp, DocumentState state)
{ {
return Interop.Pdf.PdfGenerator.GenerateFromPackage(package, Database, Target, CreatorUser, TimeStamp, State); return Interop.Pdf.PdfGenerator.GenerateFromPackage(package, database, target, creatorUser, timeStamp, state);
} }
public static Stream GeneratePdfPackageBulk(this DocumentTemplatePackage package, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, List<string> DataObjectsIds) public static Stream GeneratePdfPackageBulk(this DocumentTemplatePackage package, DiscoDataContext database, User creatorUser, DateTime timestamp, bool? insertBlankPages, List<string> dataObjectsIds)
{ {
return Interop.Pdf.PdfGenerator.GenerateBulkFromPackage(package, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjectsIds); return Interop.Pdf.PdfGenerator.GenerateBulkFromPackage(package, database, creatorUser, timestamp, insertBlankPages, dataObjectsIds);
} }
public static Stream GeneratePdfPackageBulk(this DocumentTemplatePackage package, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, List<IAttachmentTarget> DataObjects) public static Stream GeneratePdfPackageBulk(this DocumentTemplatePackage package, DiscoDataContext database, User creatorUser, DateTime timestamp, bool? insertBlankPages, List<IAttachmentTarget> dataObjects)
{ {
return Interop.Pdf.PdfGenerator.GenerateBulkFromPackage(package, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjects); return Interop.Pdf.PdfGenerator.GenerateBulkFromPackage(package, database, creatorUser, timestamp, insertBlankPages, dataObjects);
} }
public static List<bool> PdfPageHasAttachmentId(this DocumentTemplate dt, DiscoDataContext Database) public static List<bool> PdfPageHasAttachmentId(this DocumentTemplate dt, DiscoDataContext database)
{ {
string templateFilename = dt.RepositoryFilename(Database); string templateFilename = dt.RepositoryFilename(database);
if (!File.Exists(templateFilename)) if (!File.Exists(templateFilename))
throw new FileNotFoundException("PDF template not found", templateFilename); throw new FileNotFoundException("PDF template not found", templateFilename);
@@ -105,30 +132,20 @@ namespace Disco.BI.Extensions
public static void Delete(this DocumentTemplate dt, DiscoDataContext Database) public static void Delete(this DocumentTemplate dt, DiscoDataContext Database)
{ {
// Find & Rename all references // Find & Rename all references
foreach (DeviceAttachment a in Database.DeviceAttachments.Where(a => a.DocumentTemplateId == dt.Id)) void updateAttachment(IAttachment a)
{ {
a.Comments = string.Format("{0} - {1}", dt.Description, a.Comments); var comments = $"{dt.Description} - {a.Comments}";
if (a.Comments.Length > 500) if (comments.Length > 500)
a.Comments = a.Comments.Substring(0, 500); comments = comments.Substring(0, 500);
a.Comments = comments;
a.DocumentTemplateId = null; a.DocumentTemplateId = null;
a.DocumentTemplate = null;
}
foreach (JobAttachment a in Database.JobAttachments.Where(a => a.DocumentTemplateId == dt.Id))
{
a.Comments = string.Format("{0} - {1}", dt.Description, a.Comments);
if (a.Comments.Length > 500)
a.Comments = a.Comments.Substring(0, 500);
a.DocumentTemplateId = null;
a.DocumentTemplate = null;
} }
foreach (var a in Database.DeviceAttachments.Where(a => a.DocumentTemplateId == dt.Id))
updateAttachment(a);
foreach (var a in Database.JobAttachments.Where(a => a.DocumentTemplateId == dt.Id))
updateAttachment(a);
foreach (UserAttachment a in Database.UserAttachments.Where(a => a.DocumentTemplateId == dt.Id)) foreach (UserAttachment a in Database.UserAttachments.Where(a => a.DocumentTemplateId == dt.Id))
{ updateAttachment(a);
a.Comments = string.Format("{0} - {1}", dt.Description, a.Comments);
if (a.Comments.Length > 500)
a.Comments = a.Comments.Substring(0, 500);
a.DocumentTemplateId = null;
a.DocumentTemplate = null;
}
// Remove Linked Group // Remove Linked Group
ActiveDirectory.Context.ManagedGroups.Remove(DocumentTemplateDevicesManagedGroup.GetKey(dt)); ActiveDirectory.Context.ManagedGroups.Remove(DocumentTemplateDevicesManagedGroup.GetKey(dt));
@@ -139,8 +156,8 @@ namespace Disco.BI.Extensions
// Delete Template // Delete Template
string templateRepositoryFilename = dt.RepositoryFilename(Database); string templateRepositoryFilename = dt.RepositoryFilename(Database);
if (System.IO.File.Exists(templateRepositoryFilename)) if (File.Exists(templateRepositoryFilename))
System.IO.File.Delete(templateRepositoryFilename); File.Delete(templateRepositoryFilename);
// Remove from Cache // Remove from Cache
dt.FilterExpressionInvalidateCache(); dt.FilterExpressionInvalidateCache();
+101 -60
View File
@@ -1,18 +1,18 @@
using Disco.BI.Extensions; using Disco.BI.Extensions;
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.BI.Expressions;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Documents; using Disco.Models.Services.Documents;
using Disco.Models.Services.Expressions.Extensions;
using Disco.Services; using Disco.Services;
using Disco.Services.Documents; using Disco.Services.Documents;
using Disco.Services.Expressions; using Disco.Services.Expressions;
using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Tasks;
using Disco.Services.Users; using Disco.Services.Users;
using iTextSharp.text.pdf; using iTextSharp.text.pdf;
using iTextSharp.text.pdf.codec; using iTextSharp.text.pdf.codec;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -21,16 +21,16 @@ namespace Disco.BI.Interop.Pdf
{ {
public static class PdfGenerator public static class PdfGenerator
{ {
public static Stream GenerateBulkFromPackage(DocumentTemplatePackage package, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, List<IAttachmentTarget> DataObjects) public static Stream GenerateBulkFromPackage(DocumentTemplatePackage package, DiscoDataContext database, User creatorUser, DateTime timestamp, bool? insertBlankPages, List<IAttachmentTarget> dataObjects)
{ {
if (DataObjects.Count > 0) if (dataObjects.Count > 0)
{ {
List<Stream> generatedPdfs = new List<Stream>(DataObjects.Count); List<Stream> generatedPdfs = new List<Stream>(dataObjects.Count);
using (var state = DocumentState.DefaultState()) using (var state = DocumentState.DefaultState())
{ {
foreach (var d in DataObjects) foreach (var d in dataObjects)
{ {
generatedPdfs.Add(package.GeneratePdfPackage(Database, d, CreatorUser, Timestamp, state)); generatedPdfs.Add(package.GeneratePdfPackage(database, d, creatorUser, timestamp, state));
state.SequenceNumber++; state.SequenceNumber++;
state.FlushScopeCache(); state.FlushScopeCache();
} }
@@ -41,7 +41,7 @@ namespace Disco.BI.Interop.Pdf
} }
else else
{ {
Stream bulkPdf = Utilities.JoinPdfs(package.InsertBlankPages || InsertBlankPages, generatedPdfs); Stream bulkPdf = Utilities.JoinPdfs(insertBlankPages.GetValueOrDefault(package.InsertBlankPages), generatedPdfs);
foreach (Stream singlePdf in generatedPdfs) foreach (Stream singlePdf in generatedPdfs)
singlePdf.Dispose(); singlePdf.Dispose();
return bulkPdf; return bulkPdf;
@@ -50,25 +50,25 @@ namespace Disco.BI.Interop.Pdf
return null; return null;
} }
public static Stream GenerateBulkFromPackage(DocumentTemplatePackage package, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, List<string> DataObjectsIds) public static Stream GenerateBulkFromPackage(DocumentTemplatePackage package, DiscoDataContext database, User creatorUser, DateTime timestamp, bool? insertBlankPages, List<string> dataObjectsIds)
{ {
List<IAttachmentTarget> DataObjects; List<IAttachmentTarget> DataObjects;
switch (package.Scope) switch (package.Scope)
{ {
case AttachmentTypes.Device: case AttachmentTypes.Device:
DataObjects = Database.Devices.Where(d => DataObjectsIds.Contains(d.SerialNumber)).ToList<IAttachmentTarget>(); DataObjects = database.Devices.Where(d => dataObjectsIds.Contains(d.SerialNumber)).ToList<IAttachmentTarget>();
break; break;
case AttachmentTypes.Job: case AttachmentTypes.Job:
int[] intDataObjectsIds = DataObjectsIds.Select(i => int.Parse(i)).ToArray(); int[] intDataObjectsIds = dataObjectsIds.Select(i => int.Parse(i)).ToArray();
DataObjects = Database.Jobs.Where(j => intDataObjectsIds.Contains(j.Id)).ToList<IAttachmentTarget>(); DataObjects = database.Jobs.Where(j => intDataObjectsIds.Contains(j.Id)).ToList<IAttachmentTarget>();
break; break;
case AttachmentTypes.User: case AttachmentTypes.User:
DataObjects = new List<IAttachmentTarget>(DataObjectsIds.Count); DataObjects = new List<IAttachmentTarget>(dataObjectsIds.Count);
for (int idIndex = 0; idIndex < DataObjectsIds.Count; idIndex++) for (int idIndex = 0; idIndex < dataObjectsIds.Count; idIndex++)
{ {
string dataObjectId = DataObjectsIds[idIndex]; string dataObjectId = dataObjectsIds[idIndex];
var user = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(dataObjectId), Database, true); var user = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(dataObjectId), database, true);
if (user == null) if (user == null)
throw new Exception($"Unknown Username specified: {dataObjectId}"); throw new Exception($"Unknown Username specified: {dataObjectId}");
DataObjects.Add(user); DataObjects.Add(user);
@@ -78,12 +78,12 @@ namespace Disco.BI.Interop.Pdf
throw new InvalidOperationException("Invalid DocumentType Scope"); throw new InvalidOperationException("Invalid DocumentType Scope");
} }
return GenerateBulkFromPackage(package, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjects); return GenerateBulkFromPackage(package, database, creatorUser, timestamp, insertBlankPages, DataObjects);
} }
public static Stream GenerateFromPackage(DocumentTemplatePackage package, DiscoDataContext Database, IAttachmentTarget Data, User CreatorUser, DateTime Timestamp, DocumentState State) public static Stream GenerateFromPackage(DocumentTemplatePackage package, DiscoDataContext database, IAttachmentTarget data, User creatorUser, DateTime timestamp, DocumentState state)
{ {
var templates = package.GetDocumentTemplates(Database); var templates = package.GetDocumentTemplates(database);
if (templates.Count == 0) if (templates.Count == 0)
return null; return null;
@@ -92,21 +92,21 @@ namespace Disco.BI.Interop.Pdf
string generateExpressionResult = null; string generateExpressionResult = null;
if (generateExpression) if (generateExpression)
generateExpressionResult = package.EvaluateOnGenerateExpression(Data, Database, CreatorUser, Timestamp, State); generateExpressionResult = package.EvaluateOnGenerateExpression(data, database, creatorUser, timestamp, state);
List<Stream> generatedPdfs = new List<Stream>(templates.Count); List<Stream> generatedPdfs = new List<Stream>(templates.Count);
foreach (var template in templates) foreach (var template in templates)
{ {
generatedPdfs.Add(template.GeneratePdf(Database, Data, CreatorUser, Timestamp, State, true)); generatedPdfs.Add(template.GeneratePdf(database, data, creatorUser, timestamp, state, true));
State.SequenceNumber++; state.SequenceNumber++;
State.FlushScopeCache(); state.FlushScopeCache();
} }
if (generateExpression) if (generateExpression)
DocumentsLog.LogDocumentPackageGenerated(package, Data, CreatorUser, generateExpressionResult); DocumentsLog.LogDocumentPackageGenerated(package, data, creatorUser, generateExpressionResult);
else else
DocumentsLog.LogDocumentPackageGenerated(package, Data, CreatorUser); DocumentsLog.LogDocumentPackageGenerated(package, data, creatorUser);
if (generatedPdfs.Count == 1) if (generatedPdfs.Count == 1)
{ {
@@ -120,16 +120,20 @@ namespace Disco.BI.Interop.Pdf
return bulkPdf; return bulkPdf;
} }
} }
public static Stream GenerateBulkFromTemplate(DocumentTemplate dt, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, params IAttachmentTarget[] DataObjects) public static Stream GenerateBulkFromTemplate(DocumentTemplate dt, DiscoDataContext database, User creatorUser, DateTime timestamp, bool insertBlankPages, List<IAttachmentTarget> dataObjects, IScheduledTaskStatus taskStatus)
{ {
if (DataObjects.Length > 0) if (dataObjects.Count > 0)
{ {
List<Stream> generatedPdfs = new List<Stream>(DataObjects.Length); List<Stream> generatedPdfs = new List<Stream>(dataObjects.Count);
var progressPerDoc = 80d / dataObjects.Count;
var progressDoc = 10d;
using (var state = DocumentState.DefaultState()) using (var state = DocumentState.DefaultState())
{ {
foreach (var d in DataObjects) taskStatus.UpdateStatus(10, "Rendering", "Starting");
foreach (var d in dataObjects)
{ {
generatedPdfs.Add(dt.GeneratePdf(Database, d, CreatorUser, Timestamp, state, true)); taskStatus.UpdateStatus(progressDoc += progressPerDoc, $"Rendering {d.AttachmentReferenceId}");
generatedPdfs.Add(dt.GeneratePdf(database, d, creatorUser, timestamp, state, true));
state.SequenceNumber++; state.SequenceNumber++;
state.FlushScopeCache(); state.FlushScopeCache();
} }
@@ -140,7 +144,8 @@ namespace Disco.BI.Interop.Pdf
} }
else else
{ {
Stream bulkPdf = Utilities.JoinPdfs(InsertBlankPages, generatedPdfs); taskStatus.UpdateStatus(90, "Merging", "Merging documents");
Stream bulkPdf = Utilities.JoinPdfs(insertBlankPages, generatedPdfs);
foreach (Stream singlePdf in generatedPdfs) foreach (Stream singlePdf in generatedPdfs)
singlePdf.Dispose(); singlePdf.Dispose();
return bulkPdf; return bulkPdf;
@@ -149,38 +154,60 @@ namespace Disco.BI.Interop.Pdf
return null; return null;
} }
public static Stream GenerateBulkFromTemplate(DocumentTemplate dt, DiscoDataContext Database, User CreatorUser, DateTime Timestamp, bool InsertBlankPages, params string[] DataObjectsIds) public static Stream GenerateBulkFromTemplate(DocumentTemplate dt, DiscoDataContext database, User creatorUser, DateTime timestamp, bool insertBlankPages, List<string> dataObjectsIds, IScheduledTaskStatus taskStatus)
{ {
IAttachmentTarget[] DataObjects; Dictionary<string, IAttachmentTarget> dataObjectLookup;
List<string> dataObjectIds = dataObjectsIds;
taskStatus.UpdateStatus(0, "Resolving targets", "Resolving render targets");
switch (dt.Scope) switch (dt.Scope)
{ {
case DocumentTemplate.DocumentTemplateScopes.Device: case DocumentTemplate.DocumentTemplateScopes.Device:
DataObjects = Database.Devices.Where(d => DataObjectsIds.Contains(d.SerialNumber)).ToArray(); dataObjectLookup = database.Devices.Where(d => dataObjectsIds.Contains(d.SerialNumber)).AsEnumerable().Cast<IAttachmentTarget>().ToDictionary(i => i.AttachmentReferenceId, StringComparer.OrdinalIgnoreCase);
break; break;
case DocumentTemplate.DocumentTemplateScopes.Job: case DocumentTemplate.DocumentTemplateScopes.Job:
int[] intDataObjectsIds = DataObjectsIds.Select(i => int.Parse(i)).ToArray(); var intDataObjectsIds = dataObjectsIds.Select(i => int.Parse(i)).ToList();
DataObjects = Database.Jobs.Where(j => intDataObjectsIds.Contains(j.Id)).ToArray(); dataObjectLookup = database.Jobs.Where(j => intDataObjectsIds.Contains(j.Id)).AsEnumerable().Cast<IAttachmentTarget>().ToDictionary(i => i.AttachmentReferenceId, StringComparer.OrdinalIgnoreCase);
break; break;
case DocumentTemplate.DocumentTemplateScopes.User: case DocumentTemplate.DocumentTemplateScopes.User:
DataObjects = new IAttachmentTarget[DataObjectsIds.Length]; dataObjectLookup = new Dictionary<string, IAttachmentTarget>(dataObjectsIds.Count, StringComparer.OrdinalIgnoreCase);
for (int idIndex = 0; idIndex < DataObjectsIds.Length; idIndex++) dataObjectIds = new List<string>(dataObjectsIds.Count);
foreach (var userId in dataObjectsIds)
{ {
string dataObjectId = DataObjectsIds[idIndex]; var user = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(userId), database, true);
if (user == null)
DataObjects[idIndex] = UserService.GetUser(ActiveDirectory.ParseDomainAccountId(dataObjectId), Database, true); {
if (DataObjects[idIndex] == null) dataObjectIds.Add(userId);
throw new Exception($"Unknown Username specified: {dataObjectId}"); continue;
}
dataObjectIds.Add(user.UserId);
dataObjectLookup.Add(user.UserId, user);
} }
break; break;
default: default:
throw new InvalidOperationException("Invalid DocumentType Scope"); throw new InvalidOperationException("Invalid DocumentType Scope");
} }
return GenerateBulkFromTemplate(dt, Database, CreatorUser, Timestamp, InsertBlankPages, DataObjects); // recreate list to honor the sort-order provided in DataObjectsIds
var dataObjects = new List<IAttachmentTarget>(dataObjectsIds.Count);
var missingIds = new List<string>();
foreach (var id in dataObjectIds)
{
if (dataObjectLookup.TryGetValue(id, out var dataObject))
dataObjects.Add(dataObject);
else
missingIds.Add(id);
}
if (missingIds.Any())
{
throw new Exception($"Unknown id specified: {string.Join("; ", missingIds)}");
} }
public static Stream GenerateFromTemplate(DocumentTemplate dt, DiscoDataContext Database, IAttachmentTarget Data, User CreatorUser, DateTime TimeStamp, DocumentState State, bool FlattenFields = false) return GenerateBulkFromTemplate(dt, database, creatorUser, timestamp, insertBlankPages, dataObjects, taskStatus);
}
public static Stream GenerateFromTemplate(DocumentTemplate dt, DiscoDataContext Database, IAttachmentTarget Data, User CreatorUser, DateTime TimeStamp, DocumentState State, bool flattenFields = false)
{ {
// Validate Data // Validate Data
switch (dt.Scope) switch (dt.Scope)
@@ -205,9 +232,9 @@ namespace Disco.BI.Interop.Pdf
// Override FlattenFields if Document Template instructs. // Override FlattenFields if Document Template instructs.
if (dt.FlattenForm) if (dt.FlattenForm)
FlattenFields = true; flattenFields = true;
ConcurrentDictionary<string, Expression> expressionCache = dt.PdfExpressionsFromCache(Database); var expressionCache = dt.PdfExpressionsFromCache(Database);
string templateFilename = dt.RepositoryFilename(Database); string templateFilename = dt.RepositoryFilename(Database);
PdfReader pdfReader = new PdfReader(templateFilename); PdfReader pdfReader = new PdfReader(templateFilename);
@@ -215,10 +242,10 @@ namespace Disco.BI.Interop.Pdf
MemoryStream pdfGeneratedStream = new MemoryStream(); MemoryStream pdfGeneratedStream = new MemoryStream();
PdfStamper pdfStamper = new PdfStamper(pdfReader, pdfGeneratedStream); PdfStamper pdfStamper = new PdfStamper(pdfReader, pdfGeneratedStream);
pdfStamper.FormFlattening = FlattenFields; pdfStamper.FormFlattening = flattenFields;
pdfStamper.Writer.CloseStream = false; pdfStamper.Writer.CloseStream = false;
IDictionary expressionVariables = Expression.StandardVariables(dt, Database, CreatorUser, TimeStamp, State); IDictionary expressionVariables = Expression.StandardVariables(dt, Database, CreatorUser, TimeStamp, State, Data);
foreach (string pdfFieldKey in pdfStamper.AcroFields.Fields.Keys) foreach (string pdfFieldKey in pdfStamper.AcroFields.Fields.Keys)
{ {
@@ -226,8 +253,8 @@ namespace Disco.BI.Interop.Pdf
{ {
AcroFields.Item fields = pdfStamper.AcroFields.Fields[pdfFieldKey]; AcroFields.Item fields = pdfStamper.AcroFields.Fields[pdfFieldKey];
string fieldValue = dt.CreateUniqueIdentifier(Database, Data, CreatorUser, TimeStamp, 0).ToJson(); string fieldValue = dt.CreateUniqueIdentifier(Database, Data, CreatorUser, TimeStamp, 0).ToJson();
if (FlattenFields) if (flattenFields)
pdfStamper.AcroFields.SetField(pdfFieldKey, String.Empty); pdfStamper.AcroFields.SetField(pdfFieldKey, string.Empty);
else else
pdfStamper.AcroFields.SetField(pdfFieldKey, fieldValue); pdfStamper.AcroFields.SetField(pdfFieldKey, fieldValue);
@@ -241,16 +268,15 @@ namespace Disco.BI.Interop.Pdf
var pageUniqueIdBytes = pageUniqueId.ToQRCodeBytes(); var pageUniqueIdBytes = pageUniqueId.ToQRCodeBytes();
// Encode to QRCode byte array // Encode to QRCode byte array
var pageUniqueIdWidth = (int)pdfFieldPosition.position.Width; var pageUniqueIdEncoded = QRCodeBinaryEncoder.Encode(pageUniqueIdBytes, out var qrWidth, out var qrHeight);
var pageUniqueIdHeight = (int)pdfFieldPosition.position.Height;
var pageUniqueIdEncoded = QRCodeBinaryEncoder.Encode(pageUniqueIdBytes, pageUniqueIdWidth, pageUniqueIdHeight);
// Encode byte array to Image // Encode byte array to Image
var pageUniqueIdImageData = CCITTG4Encoder.Compress(pageUniqueIdEncoded, pageUniqueIdWidth, pageUniqueIdHeight); var pageUniqueIdImageData = CCITTG4Encoder.Compress(pageUniqueIdEncoded, qrWidth, qrHeight);
var pageUniqueIdImage = iTextSharp.text.Image.GetInstance(pageUniqueIdWidth, pageUniqueIdHeight, false, 256, 1, pageUniqueIdImageData, null); var pageUniqueIdImage = iTextSharp.text.Image.GetInstance(qrWidth, qrHeight, false, 256, 1, pageUniqueIdImageData, null);
// Add to the pdf page // Add to the pdf page
pageUniqueIdImage.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom); pageUniqueIdImage.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom);
pageUniqueIdImage.ScaleToFit(pdfFieldPosition.position.Width, pdfFieldPosition.position.Height);
pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pageUniqueIdImage); pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pageUniqueIdImage);
} }
// Hide Fields // Hide Fields
@@ -270,8 +296,7 @@ namespace Disco.BI.Interop.Pdf
} }
else else
{ {
Expression fieldExpression = null; if (expressionCache.TryGetValue(pdfFieldKey, out var fieldExpression))
if (expressionCache.TryGetValue(pdfFieldKey, out fieldExpression))
{ {
if (fieldExpression.IsDynamic) if (fieldExpression.IsDynamic)
{ {
@@ -288,8 +313,24 @@ namespace Disco.BI.Interop.Pdf
for (int pdfFieldOrdinal = 0; pdfFieldOrdinal < fields.Size; pdfFieldOrdinal++) for (int pdfFieldOrdinal = 0; pdfFieldOrdinal < fields.Size; pdfFieldOrdinal++)
{ {
AcroFields.FieldPosition pdfFieldPosition = pdfFieldPositions[pdfFieldOrdinal]; AcroFields.FieldPosition pdfFieldPosition = pdfFieldPositions[pdfFieldOrdinal];
iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(imageResult.GetImage((int)pdfFieldPosition.position.Width, (int)pdfFieldPosition.position.Height));
iTextSharp.text.Image pdfImage;
var imageWidth = (int)(pdfFieldPosition.position.Width * 1.6);
var imageHeight = (int)(pdfFieldPosition.position.Height * 1.6);
if (imageResult.Format == ImageExpressionFormat.Jpeg || imageResult.Format == ImageExpressionFormat.Png)
{
pdfImage = iTextSharp.text.Image.GetInstance(imageResult.GetImage(imageWidth, imageHeight));
}
else if (imageResult.Format == ImageExpressionFormat.CcittG4)
{
var imageData = imageResult.GetImage(out imageWidth, out imageHeight);
pdfImage = iTextSharp.text.Image.GetInstance(imageWidth, imageHeight, false, 256, 1, imageData.GetBuffer(), null);
}
else
throw new NotSupportedException($"Unexpected image format {imageResult.Format}");
pdfImage.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom); pdfImage.SetAbsolutePosition(pdfFieldPosition.position.Left, pdfFieldPosition.position.Bottom);
pdfImage.ScaleToFit(pdfFieldPosition.position.Width, pdfFieldPosition.position.Height);
pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pdfImage); pdfStamper.GetOverContent(pdfFieldPosition.page).AddImage(pdfImage);
} }
if (!fieldExpressionResult.Item2 && !imageResult.ShowField) if (!fieldExpressionResult.Item2 && !imageResult.ShowField)
@@ -355,7 +396,7 @@ namespace Disco.BI.Interop.Pdf
TechUserId = CreatorUser.UserId, TechUserId = CreatorUser.UserId,
Timestamp = DateTime.Now Timestamp = DateTime.Now
}; };
jl.Comments = string.Format("# Document Generated\r\n**{0}** [{1}]", dt.Description, dt.Id); jl.Comments = $"# Document Generated\r\n**{dt.Description}** [{dt.Id}]";
Database.JobLogs.Add(jl); Database.JobLogs.Add(jl);
} }
+8 -1
View File
@@ -1,5 +1,7 @@
using iTextSharp.text; using iTextSharp.text;
using iTextSharp.text.pdf; using iTextSharp.text.pdf;
using iTextSharp.text.pdf.codec;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -7,10 +9,15 @@ namespace Disco.BI.Interop.Pdf
{ {
public static class Utilities public static class Utilities
{ {
public static Func<byte[], int, int, byte[]> GetCCITTG4EncoderCompressDelegate()
{
return CCITTG4Encoder.Compress;
}
public static Stream JoinPdfs(bool InsertBlankPages, List<Stream> Pdfs) public static Stream JoinPdfs(bool InsertBlankPages, List<Stream> Pdfs)
{ {
if (Pdfs.Count == 0) if (Pdfs.Count == 0)
throw new System.ArgumentNullException(nameof(Pdfs)); throw new ArgumentNullException(nameof(Pdfs));
// Only One PDF - Possible Reference Bug v's Memory/Speed (Returning Param Memory Stream) // Only One PDF - Possible Reference Bug v's Memory/Speed (Returning Param Memory Stream)
if (Pdfs.Count == 1) if (Pdfs.Count == 1)
+1 -3
View File
@@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco</RootNamespace> <RootNamespace>Disco</RootNamespace>
<AssemblyName>Disco.BI</AssemblyName> <AssemblyName>Disco.BI</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
@@ -53,7 +53,6 @@
<Reference Include="System.DirectoryServices" /> <Reference Include="System.DirectoryServices" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Extensions, Version=2.2.22.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="System.Net.Http.Extensions, Version=2.2.22.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll</HintPath> <HintPath>..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@@ -62,7 +61,6 @@
<HintPath>..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll</HintPath> <HintPath>..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" /> <Reference Include="System.ServiceModel" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
+3 -3
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.2.16320.1300")] [assembly: AssemblyVersion("2.5.25262.0000")]
[assembly: AssemblyFileVersion("2.2.16320.1300")] [assembly: AssemblyFileVersion("2.5.25262.0000")]
+1 -1
View File
@@ -19,7 +19,7 @@ namespace Disco.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
+16 -4
View File
@@ -16,7 +16,7 @@
</defaultConnectionFactory> </defaultConnectionFactory>
</entityFramework> </entityFramework>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup> </startup>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
@@ -30,7 +30,7 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" /> <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Web.Razor" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="System.Web.Razor" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@@ -46,11 +46,23 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" /> <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" /> <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.3" newVersion="4.1.1.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
+24 -5
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,9 +9,8 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco.Client</RootNamespace> <RootNamespace>Disco.Client</RootNamespace>
<AssemblyName>Disco.Client</AssemblyName> <AssemblyName>Disco.Client</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages> <RestorePackages>true</RestorePackages>
</PropertyGroup> </PropertyGroup>
@@ -24,6 +23,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@@ -33,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject>Disco.Client.Program</StartupObject> <StartupObject>Disco.Client.Program</StartupObject>
@@ -48,8 +49,8 @@
<ApplicationManifest>Properties\app.manifest</ApplicationManifest> <ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.13.0.2\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
@@ -65,12 +66,24 @@
<Compile Include="..\Disco.Models\ClientServices\Enrol.cs"> <Compile Include="..\Disco.Models\ClientServices\Enrol.cs">
<Link>Models\ClientServices\Enrol.cs</Link> <Link>Models\ClientServices\Enrol.cs</Link>
</Compile> </Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\BaseBoard.cs">
<Link>Models\ClientServices\EnrolmentInformation\BaseBoard.cs</Link>
</Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\Battery.cs">
<Link>Models\ClientServices\EnrolmentInformation\Battery.cs</Link>
</Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\Bios.cs">
<Link>Models\ClientServices\EnrolmentInformation\Bios.cs</Link>
</Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\Certificate.cs"> <Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\Certificate.cs">
<Link>Models\ClientServices\EnrolmentInformation\Certificate.cs</Link> <Link>Models\ClientServices\EnrolmentInformation\Certificate.cs</Link>
</Compile> </Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\CertificateStore.cs"> <Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\CertificateStore.cs">
<Link>Models\ClientServices\EnrolmentInformation\CertificateStore.cs</Link> <Link>Models\ClientServices\EnrolmentInformation\CertificateStore.cs</Link>
</Compile> </Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\ComputerSystem.cs">
<Link>Models\ClientServices\EnrolmentInformation\ComputerSystem.cs</Link>
</Compile>
<Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\DeviceHardware.cs"> <Compile Include="..\Disco.Models\ClientServices\EnrolmentInformation\DeviceHardware.cs">
<Link>Models\ClientServices\EnrolmentInformation\DeviceHardware.cs</Link> <Link>Models\ClientServices\EnrolmentInformation\DeviceHardware.cs</Link>
</Compile> </Compile>
@@ -128,6 +141,7 @@
<Compile Include="Extensions\EnrolExtensions.cs" /> <Compile Include="Extensions\EnrolExtensions.cs" />
<Compile Include="Extensions\WhoAmIExtensions.cs" /> <Compile Include="Extensions\WhoAmIExtensions.cs" />
<Compile Include="Interop\Certificates.cs" /> <Compile Include="Interop\Certificates.cs" />
<Compile Include="Interop\EndpointDiscovery.cs" />
<Compile Include="Interop\Hardware.cs" /> <Compile Include="Interop\Hardware.cs" />
<Compile Include="Interop\LocalAuthentication.cs" /> <Compile Include="Interop\LocalAuthentication.cs" />
<Compile Include="Interop\Native\NetworkConnectionStatuses.cs" /> <Compile Include="Interop\Native\NetworkConnectionStatuses.cs" />
@@ -169,6 +183,11 @@
"$(ProjectDir)Package Creation\7z.exe" a -tzip "$(ProjectDir)Package Creation\PreparationClient.zip" "$(TargetDir)*.*" -x!*.tmp -x!*.vshost.* -x!Newtonsoft.Json.xml -x!*.pdb "$(ProjectDir)Package Creation\7z.exe" a -tzip "$(ProjectDir)Package Creation\PreparationClient.zip" "$(TargetDir)*.*" -x!*.tmp -x!*.vshost.* -x!Newtonsoft.Json.xml -x!*.pdb
COPY "$(ProjectDir)Package Creation\PreparationClient.zip" "$(ProjectDir)..\Disco.Web\ClientBin"</PostBuildEvent> COPY "$(ProjectDir)Package Creation\PreparationClient.zip" "$(ProjectDir)..\Disco.Web\ClientBin"</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_StartDate="2000/1/1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
+12 -14
View File
@@ -1,31 +1,27 @@
using System; using Disco.Client.Extensions;
using System.Collections.Generic; using Newtonsoft.Json;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.Text;
using Disco.Client.Extensions;
using Newtonsoft.Json;
namespace Disco.Client namespace Disco.Client
{ {
public static class ErrorReporting public static class ErrorReporting
{ {
private const string ServicePathTemplate = "http://DISCO:9292/Services/Client/ClientError";
public static string DeviceIdentifier { get; set; } public static string DeviceIdentifier { get; set; }
public static string EnrolmentSessionId { get; set; } public static string EnrolmentSessionId { get; set; }
public static void ReportError(Exception Ex, bool ReportToServer) public static void ReportError(Exception exception, bool reportToServer)
{ {
bool isClientServiceException = Ex is ClientServiceException; bool isClientServiceException = exception is ClientServiceException;
ErrorReport report = new ErrorReport() ErrorReport report = new ErrorReport()
{ {
DeviceIdentifier = DeviceIdentifier, DeviceIdentifier = DeviceIdentifier,
SessionId = EnrolmentSessionId, SessionId = EnrolmentSessionId,
JsonException = Ex.IntenseExceptionSerialization() JsonException = exception.IntenseExceptionSerialization()
}; };
try try
@@ -41,7 +37,7 @@ namespace Disco.Client
catch (Exception) { } catch (Exception) { }
// Don't log server errors back to the server // Don't log server errors back to the server
if (!isClientServiceException && ReportToServer) if (!isClientServiceException && reportToServer)
{ {
try try
{ {
@@ -52,7 +48,7 @@ namespace Disco.Client
try try
{ {
Presentation.WriteFatalError(Ex); Presentation.WriteFatalError(exception);
} }
catch (Exception) { } catch (Exception) { }
} }
@@ -88,7 +84,9 @@ namespace Disco.Client
string reportJson = JsonConvert.SerializeObject(report); string reportJson = JsonConvert.SerializeObject(report);
string reportResponse; string reportResponse;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(ServicePathTemplate); var serverUri = new Uri(Program.ServerUrl ?? new Uri("http://disco:9292"), "/Services/Client/ClientError");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serverUri);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"; request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
request.ContentType = "application/json"; request.ContentType = "application/json";
request.Method = WebRequestMethods.Http.Post; request.Method = WebRequestMethods.Http.Post;
@@ -108,7 +106,7 @@ namespace Disco.Client
} }
} }
System.Diagnostics.Debug.WriteLine("Error Report Logged to Server; Response: {0}", reportResponse); Debug.WriteLine($"Error Report Logged to Server; Response: {reportResponse}");
} }
#endregion #endregion
@@ -1,32 +1,25 @@
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
public static class ClientServicesExtensions internal static class ClientServicesExtensions
{ {
#if DEBUG public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> service, bool authenticated)
public const string ServicePathAuthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Authenticated/{0}";
public const string ServicePathUnauthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Unauthenticated/{0}";
#else
public const string ServicePathAuthenticatedTemplate = "http://DISCO:9292/Services/Client/Authenticated/{0}";
public const string ServicePathUnauthenticatedTemplate = "http://DISCO:9292/Services/Client/Unauthenticated/{0}";
#endif
public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> Service, bool Authenticated)
{ {
ResponseType serviceResponse; ResponseType serviceResponse;
string serviceUrl; Uri serviceUrl;
if (Authenticated) if (authenticated)
serviceUrl = string.Format(ServicePathAuthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Authenticated/{service.Feature}");
else else
serviceUrl = string.Format(ServicePathUnauthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Unauthenticated/{service.Feature}");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(serviceUrl); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"; request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
request.ContentType = "application/json"; request.ContentType = "application/json";
request.Method = WebRequestMethods.Http.Post; request.Method = WebRequestMethods.Http.Post;
@@ -39,7 +32,7 @@ namespace Disco.Client.Extensions
{ {
using (var jsonWriter = new JsonTextWriter(requestWriter)) using (var jsonWriter = new JsonTextWriter(requestWriter))
{ {
jsonSerializer.Serialize(jsonWriter, Service); jsonSerializer.Serialize(jsonWriter, service);
} }
} }
+54 -27
View File
@@ -2,14 +2,12 @@
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Diagnostics; using System.Runtime.InteropServices;
using System.IO;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
public static class EnrolExtensions internal static class EnrolExtensions
{ {
public static void Build(this Enrol enrol) public static void Build(this Enrol enrol)
{ {
enrol.ComputerName = Environment.MachineName; enrol.ComputerName = Environment.MachineName;
@@ -41,6 +39,9 @@ namespace Disco.Client.Extensions
if (!string.IsNullOrEmpty(enrolResponse.ErrorMessage)) if (!string.IsNullOrEmpty(enrolResponse.ErrorMessage))
throw new ClientServiceException("Enrolment", enrolResponse.ErrorMessage); throw new ClientServiceException("Enrolment", enrolResponse.ErrorMessage);
if (enrolResponse.IsPending)
return;
// Offline Domain Join // Offline Domain Join
bool requireReboot = enrolResponse.ApplyOfflineDomainJoin(); bool requireReboot = enrolResponse.ApplyOfflineDomainJoin();
@@ -59,6 +60,28 @@ namespace Disco.Client.Extensions
Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall; Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall;
} }
[Flags]
private enum NETSETUP_PROVISION_FLAGS : int
{
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 0x00000001,
NETSETUP_PROVISION_REUSE_ACCOUNT = 0x00000002,
NETSETUP_PROVISION_USE_DEFAULT_PASSWORD = 0x00000004,
NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH = 0x00000008,
NETSETUP_PROVISION_ROOT_CA_CERTS = 0x00000010,
NETSETUP_PROVISION_PERSISTENTSITE = 0x00000020,
NETSETUP_PROVISION_ONLINE_CALLER = 0x40000000,
NETSETUP_PROVISION_CHECK_PWD_ONLY = unchecked((int)0x80000000),
}
[DllImport("Netapi32.dll", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int NetRequestOfflineDomainJoin(
[In] IntPtr pProvisionBinData,
[In, MarshalAs(UnmanagedType.I4)] int cbProvisionBinDataSize,
[In, MarshalAs(UnmanagedType.I4)] NETSETUP_PROVISION_FLAGS dwOptions,
[In, MarshalAs(UnmanagedType.LPWStr)] string lpWindowsPath
);
/// <summary> /// <summary>
/// Processes a Client Service Enrol Response for Offline Domain Join Actions /// Processes a Client Service Enrol Response for Offline Domain Join Actions
/// </summary> /// </summary>
@@ -70,38 +93,39 @@ namespace Disco.Client.Extensions
{ {
Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500); Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
string odjFile = Path.GetTempFileName(); var provisionData = Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest);
File.WriteAllBytes(odjFile, Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest)); string systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
string odjWindowsPath = Environment.GetEnvironmentVariable("SystemRoot"); var provisionDataPointer = Marshal.AllocCoTaskMem(provisionData.Length);
string odjProcessArguments = $"/REQUESTODJ /LOADFILE \"{odjFile}\" /WINDOWSPATH \"{odjWindowsPath}\" /LOCALOS"; Marshal.Copy(provisionData, 0, provisionDataPointer, provisionData.Length);
var joinResult = default(int);
ProcessStartInfo odjProcessStartInfo = new ProcessStartInfo("DJOIN.EXE", odjProcessArguments) try
{ {
CreateNoWindow = true, joinResult = NetRequestOfflineDomainJoin(provisionDataPointer, provisionData.Length, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_ONLINE_CALLER, systemRoot);
ErrorDialog = false, }
LoadUserProfile = true, finally
RedirectStandardOutput = true, {
UseShellExecute = false Marshal.FreeCoTaskMem(provisionDataPointer);
};
string odjResult;
using (Process odjProcess = System.Diagnostics.Process.Start(odjProcessStartInfo))
{
odjResult = odjProcess.StandardOutput.ReadToEnd();
odjProcess.WaitForExit(20000); // 20 Seconds
} }
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Result:\r\n{odjResult}", true, -1, 3000);
if (File.Exists(odjFile)) if (joinResult != 0)
File.Delete(odjFile); {
var win32Exception = new System.ComponentModel.Win32Exception(joinResult);
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]", true, -1, 3000);
throw new InvalidOperationException($"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]");
}
else
{
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Succeeded", true, -1, 2000);
}
// Flush Logged-On History // Flush Logged-On History
if (!string.IsNullOrEmpty(enrolResponse.DomainName)) if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.DomainName))
{ {
using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true)) using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.SetValue("DefaultDomainName", enrolResponse.DomainName, RegistryValueKind.String); regWinlogon.SetValue("DefaultDomainName", enrolResponse.DomainName, RegistryValueKind.String);
regWinlogon.SetValue("DefaultUserName", String.Empty, RegistryValueKind.String); regWinlogon.SetValue("DefaultUserName", string.Empty, RegistryValueKind.String);
} }
using (RegistryKey regLogonUI = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI", true)) using (RegistryKey regLogonUI = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI", true))
{ {
@@ -130,8 +154,11 @@ namespace Disco.Client.Extensions
Presentation.UpdateStatus("Enrolling Device", $"Configuring the device owner:\r\n{enrolResponse.AssignedUserDescription} ({enrolResponse.AssignedUserDomain}\\{enrolResponse.AssignedUserUsername})", true, -1, 3000); Presentation.UpdateStatus("Enrolling Device", $"Configuring the device owner:\r\n{enrolResponse.AssignedUserDescription} ({enrolResponse.AssignedUserDomain}\\{enrolResponse.AssignedUserUsername})", true, -1, 3000);
if (enrolResponse.AssignedUserIsLocalAdmin) if (enrolResponse.AssignedUserIsLocalAdmin)
Interop.LocalAuthentication.AddLocalGroupMembership("Administrators", enrolResponse.AssignedUserSID, enrolResponse.AssignedUserUsername, enrolResponse.AssignedUserDomain); LocalAuthentication.AddLocalGroupMembership("Administrators", enrolResponse.AssignedUserSID, enrolResponse.AssignedUserUsername, enrolResponse.AssignedUserDomain);
}
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.AssignedUserDomain) && !string.IsNullOrEmpty(enrolResponse.AssignedUserUsername))
{
// Make Windows think this user was the last to logon // Make Windows think this user was the last to logon
using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true)) using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
+1 -5
View File
@@ -1,9 +1,5 @@
using System; using Disco.Models.ClientServices;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Disco.Models.ClientServices;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
+1 -2
View File
@@ -105,8 +105,7 @@ namespace Disco.Client.Interop
{ {
foreach (var thumbprint in RemoveThumbprints) foreach (var thumbprint in RemoveThumbprints)
{ {
List<X509Certificate2> certificates; if (existingThumbprints.TryGetValue(thumbprint, out var certificates) && !addedThumbprints.Contains(thumbprint))
if (existingThumbprints.TryGetValue(thumbprint, out certificates) && !addedThumbprints.Contains(thumbprint))
{ {
foreach (var certificate in certificates) foreach (var certificate in certificates)
{ {
+317
View File
@@ -0,0 +1,317 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading;
namespace Disco.Client.Interop
{
internal class EndpointDiscovery
{
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
private const int DNS_ERROR_BAD_PACKET = 0x251E;
public static Tuple<Uri, string> DiscoverServer(Uri forcedServerUri)
{
// 1. Check first command line argument for server name
if (forcedServerUri != null)
return Tuple.Create(forcedServerUri, "Manual");
// 2. Check for a DNS SRV record for _discoict._tcp.domain
var domainSuffixes = new List<string>();
var primaryDomain = IPGlobalProperties.GetIPGlobalProperties().DomainName;
if (!string.IsNullOrEmpty(primaryDomain))
domainSuffixes.Add(primaryDomain);
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up);
foreach (var ni in networkInterfaces)
{
var domainSuffix = ni.GetIPProperties().DnsSuffix;
if (!string.IsNullOrWhiteSpace(domainSuffix))
{
if (domainSuffix.Equals("mshome.net", StringComparison.OrdinalIgnoreCase))
continue;
if (!domainSuffixes.Contains(domainSuffix, StringComparer.OrdinalIgnoreCase))
domainSuffixes.Add(domainSuffix);
}
}
foreach (var domain in domainSuffixes)
{
var dnsRecords = GetSRVRecords("_discoict._tcp." + domain);
if (dnsRecords.Count > 0)
{
var firstRecord = dnsRecords.OrderBy(r => r.Priority).ThenByDescending(r => r.Weight).First();
if (firstRecord.Port == 443)
return Tuple.Create(new Uri($"https://{firstRecord.Target}"), "SRV");
else
return Tuple.Create(new Uri($"https://{firstRecord.Target}:{firstRecord.Port}"), "SRV");
}
}
// 3. Detect VicSmart network and try resolving with Disco ICT Online Services
if (TryResolveVicSmartServer(domainSuffixes, out var vicSmartServerUrl))
return Tuple.Create(vicSmartServerUrl, "VicSmart");
// 4. Legacy: Ping 'disco' and assume port 9292
using (Ping p = new Ping())
{
try
{
PingReply pr = p.Send("disco", 2000);
if (pr.Status == IPStatus.Success)
return Tuple.Create(new Uri("http://disco:9292"), "Legacy");
}
catch (Exception)
{
}
}
throw new Exception("Could not locate Disco ICT server on the network.");
}
private static bool TryResolveVicSmartServer(List<string> domainSuffixes, out Uri serverUrl)
{
if (IsVicSmartNetwork(domainSuffixes))
{
var potentialVicSmartAddresses = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.SelectMany(ni => ni.GetIPProperties().UnicastAddresses)
.Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
.Select(ua => ua.Address.GetAddressBytes())
.Where(a => a[0] == 10)
.Select(a => (ushort)((a[1] >> 4) & 0x000F) | ((a[1] << 4) & 0x00F0) | ((a[2] << 12) & 0xF000) | ((a[2] << 4) & 0x0F00))
.Distinct()
.Select(a => $"{a:x4}.vicsmart.discoict.com")
.ToList();
foreach (var potentialAddress in potentialVicSmartAddresses)
{
var records = GetTxtRecords(potentialAddress);
foreach (var record in records)
{
if (!record.Content.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
continue;
if (Uri.TryCreate(record.Content, UriKind.Absolute, out var discoveredUri))
{
serverUrl = discoveredUri;
return true;
}
}
}
}
serverUrl = null;
return false;
}
private static bool IsVicSmartNetwork(List<string> domainSuffixes)
{
if (domainSuffixes.Any(s => string.Equals("services.education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase)) ||
domainSuffixes.Any(s => string.Equals("education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase))
)
return true;
IPHostEntry doeWanDnsEntry;
try
{
doeWanDnsEntry = Dns.GetHostEntry("broadband.doe.wan");
if (doeWanDnsEntry.AddressList.Length > 0)
return true;
}
catch (Exception)
{ }
return false;
}
private static List<DnsTxtRecord> GetTxtRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsTxtRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_TEXT, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsTxtRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsTxtRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_TEXT)
records.Add(DnsTxtRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private static List<DnsSrvRecord> GetSRVRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsSrvRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_SRV, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsSrvRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsSrvRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_SRV)
records.Add(DnsSrvRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private enum NativeDnsQueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum NativeDnsQueryTypes
{
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsSrvRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameTarget;
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}
private class DnsSrvRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Target { get; set; }
public int Priority { get; set; }
public int Weight { get; set; }
public int Port { get; set; }
public static DnsSrvRecord FromNativeRecord(NativeDnsSrvRecord nativeRecord)
{
return new DnsSrvRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Target = nativeRecord.pNameTarget,
Priority = nativeRecord.wPriority,
Weight = nativeRecord.wWeight,
Port = nativeRecord.wPort
};
}
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsTxtRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint dwStringLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringArray;
}
private class DnsTxtRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Content { get; set; }
public static DnsTxtRecord FromNativeRecord(NativeDnsTxtRecord nativeRecord)
{
return new DnsTxtRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Content = nativeRecord.pStringArray,
};
}
}
}
}
+469 -44
View File
@@ -1,6 +1,8 @@
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Disco.Models.ClientServices.EnrolmentInformation; using Disco.Models.ClientServices.EnrolmentInformation;
using System; using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Management; using System.Management;
@@ -40,18 +42,240 @@ namespace Disco.Client.Interop
throw new Exception($"The serial number reported by this device is over 60 characters long:\r\n{audit.SerialNumber}"); throw new Exception($"The serial number reported by this device is over 60 characters long:\r\n{audit.SerialNumber}");
} }
#warning TODO: Processors, PhysicalMemory, DiskDrives, etc audit.ApplyProcessorInformation();
audit.ApplyPhysicalMemoryInformation();
audit.ApplyDiskDriveInformation();
audit.ApplyBatteryInformation();
audit.ApplyMobileDeviceManagementInformation();
audit.NetworkAdapters = Network.GetNetworkAdapters(); audit.NetworkAdapters = Network.GetNetworkAdapters();
return audit; return audit;
} }
private static void ApplyBIOSInformation(this DeviceHardware DeviceHardware) private static void ApplyProcessorInformation(this DeviceHardware deviceHardware)
{ {
try try
{ {
using (var mSearcher = new ManagementObjectSearcher("SELECT SerialNumber, SMBIOSBIOSVersion FROM Win32_BIOS WHERE PrimaryBios=true")) using (var mSearcher = new ManagementObjectSearcher("SELECT DeviceID, Manufacturer, Name, Description, Architecture, Family, MaxClockSpeed, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor"))
{
using (var mResults = mSearcher.Get())
{
if (mResults.Count > 0)
{
var processors = new List<Processor>(mResults.Count);
foreach (var mItem in mResults.Cast<ManagementObject>())
{
if (mItem != null)
{
var processor = new Processor();
processor.DeviceID = (string)mItem.GetPropertyValue("DeviceID");
processor.Manufacturer = (string)mItem.GetPropertyValue("Manufacturer");
processor.Name = (string)mItem.GetPropertyValue("Name");
processor.Description = (string)mItem.GetPropertyValue("Description");
processor.Family = (ushort?)mItem.GetPropertyValue("Family");
processor.MaxClockSpeed = (uint?)mItem.GetPropertyValue("MaxClockSpeed");
processor.NumberOfCores = (uint?)mItem.GetPropertyValue("NumberOfCores");
processor.NumberOfLogicalProcessors = (uint?)mItem.GetPropertyValue("NumberOfLogicalProcessors");
processor.Architecture = ((ProcessorArchitectures?)(ushort?)mItem.GetPropertyValue("Architecture"))?.ToString();
processors.Add(processor);
}
}
deviceHardware.Processors = processors;
}
}
}
}
catch (Exception)
{
// ignore errors to ensure backwards compatibility
}
}
private static void ApplyPhysicalMemoryInformation(this DeviceHardware deviceHardware)
{
try
{
using (var mSearcher = new ManagementObjectSearcher("SELECT Tag, SerialNumber, Manufacturer, PartNumber, Capacity, ConfiguredClockSpeed, Speed, DeviceLocator FROM Win32_PhysicalMemory"))
{
using (var mResults = mSearcher.Get())
{
if (mResults.Count > 0)
{
var physicalMemories = new List<PhysicalMemory>(mResults.Count);
foreach (var mItem in mResults.Cast<ManagementObject>())
{
if (mItem != null)
{
var physicalMemory = new PhysicalMemory();
physicalMemory.Tag = (string)mItem.GetPropertyValue("Tag");
physicalMemory.SerialNumber = (string)mItem.GetPropertyValue("SerialNumber");
physicalMemory.Manufacturer = (string)mItem.GetPropertyValue("Manufacturer");
physicalMemory.PartNumber = (string)mItem.GetPropertyValue("PartNumber");
physicalMemory.Capacity = (ulong?)mItem.GetPropertyValue("Capacity");
physicalMemory.ConfiguredClockSpeed = (uint?)mItem.GetPropertyValue("ConfiguredClockSpeed");
physicalMemory.Speed = (uint?)mItem.GetPropertyValue("Speed");
physicalMemory.DeviceLocator = (string)mItem.GetPropertyValue("DeviceLocator");
physicalMemories.Add(physicalMemory);
}
}
deviceHardware.PhysicalMemory = physicalMemories;
}
}
}
}
catch (Exception)
{
// ignore errors to ensure backwards compatibility
}
}
private static void ApplyDiskDriveInformation(this DeviceHardware deviceHardware)
{
try
{
using (var diskSearcher = new ManagementObjectSearcher("SELECT DeviceID, Manufacturer, Model, MediaType, InterfaceType, SerialNumber, FirmwareRevision, Size FROM Win32_DiskDrive"))
{
using (var diskResults = diskSearcher.Get())
{
if (diskResults.Count > 0)
{
var diskDrives = new List<DiskDrive>(diskResults.Count);
foreach (var diskResult in diskResults.Cast<ManagementObject>())
{
if (diskResult != null)
{
var diskDrive = new DiskDrive();
diskDrive.DeviceID = (string)diskResult.GetPropertyValue("DeviceID");
diskDrive.Manufacturer = (string)diskResult.GetPropertyValue("Manufacturer");
diskDrive.Model = (string)diskResult.GetPropertyValue("Model");
diskDrive.MediaType = (string)diskResult.GetPropertyValue("MediaType");
diskDrive.InterfaceType = (string)diskResult.GetPropertyValue("InterfaceType");
diskDrive.SerialNumber = (string)diskResult.GetPropertyValue("SerialNumber");
diskDrive.FirmwareRevision = (string)diskResult.GetPropertyValue("FirmwareRevision");
diskDrive.Size = (ulong?)diskResult.GetPropertyValue("Size");
using (var partitionSearcher = new ManagementObjectSearcher($"ASSOCIATORS OF {{Win32_DiskDrive.DeviceID=\"{diskDrive.DeviceID.Replace(@"\", @"\\")}\"}} WHERE AssocClass = Win32_DiskDriveToDiskPartition"))
{
using (var partitionResults = partitionSearcher.Get())
{
if (partitionResults.Count > 0)
{
var partitions = new List<DiskDrivePartition>(partitionResults.Count);
foreach (var partitionResult in partitionResults.Cast<ManagementObject>())
{
if (partitionResult != null)
{
var partition = new DiskDrivePartition();
partition.DeviceID = (string)partitionResult.GetPropertyValue("DeviceID");
partition.Bootable = (bool?)partitionResult.GetPropertyValue("Bootable");
partition.BootPartition = (bool?)partitionResult.GetPropertyValue("BootPartition");
partition.PrimaryParition = (bool?)partitionResult.GetPropertyValue("PrimaryPartition");
partition.Size = (ulong?)partitionResult.GetPropertyValue("Size");
partition.StartingOffset = (ulong?)partitionResult.GetPropertyValue("StartingOffset");
partition.Type = (string)partitionResult.GetPropertyValue("Type");
using (var logicalSearcher = new ManagementObjectSearcher($"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID=\"{partition.DeviceID}\"}} WHERE AssocClass = Win32_LogicalDiskToPartition"))
{
using (var logicalResults = logicalSearcher.Get())
{
if (logicalResults.Count > 0)
{
foreach (var logicalResult in logicalResults.Cast<ManagementObject>().Take(1))
{
if (logicalResult != null)
{
var logical = new DiskLogical();
logical.DeviceID = (string)logicalResult.GetPropertyValue("DeviceID");
logical.Description = (string)logicalResult.GetPropertyValue("Description");
logical.DriveType = ((DiskLogicalDriveTypes?)(uint?)logicalResult.GetPropertyValue("DriveType")).ToString();
logical.MediaType = ((DiskLogicalMediaTypes?)(uint?)logicalResult.GetPropertyValue("MediaType")).ToString();
logical.FileSystem = (string)logicalResult.GetPropertyValue("FileSystem");
logical.Size = (ulong?)logicalResult.GetPropertyValue("Size");
logical.FreeSpace = (ulong?)logicalResult.GetPropertyValue("FreeSpace");
logical.VolumeName = (string)logicalResult.GetPropertyValue("VolumeName");
logical.VolumeSerialNumber = (string)logicalResult.GetPropertyValue("VolumeSerialNumber");
partition.LogicalDisk = logical;
}
}
}
}
}
partitions.Add(partition);
}
}
diskDrive.Partitions = partitions;
}
}
}
diskDrives.Add(diskDrive);
}
}
deviceHardware.DiskDrives = diskDrives;
}
}
}
}
catch (Exception)
{
// ignore errors to ensure backwards compatibility
}
}
private static void ApplyBatteryInformation(this DeviceHardware deviceHardware)
{
try
{
using (var mSearcher = new ManagementObjectSearcher("SELECT DeviceID, Availability, Chemistry, Description, DesignCapacity, DesignVoltage, FullChargeCapacity, Name FROM Win32_Battery"))
{
using (var mResults = mSearcher.Get())
{
if (mResults.Count > 0)
{
var batteries = new List<Battery>(mResults.Count);
foreach (var mItem in mResults.Cast<ManagementObject>())
{
if (mItem != null)
{
var battery = new Battery();
battery.Availability = ((BatteryAvailability?)(ushort?)mItem.GetPropertyValue("Availability"))?.ToString();
battery.Chemistry = ((BatteryChemistry?)(ushort?)mItem.GetPropertyValue("Chemistry"))?.ToString();
battery.Description = (string)mItem.GetPropertyValue("Description");
battery.DesignCapacity = (uint?)mItem.GetPropertyValue("DesignCapacity");
battery.DeviceID = (string)mItem.GetPropertyValue("DeviceID");
battery.FullChargeCapacity = (uint?)mItem.GetPropertyValue("FullChargeCapacity");
battery.Name = (string)mItem.GetPropertyValue("Name");
batteries.Add(battery);
}
}
deviceHardware.Batteries = batteries;
}
}
}
}
catch (Exception)
{
// ignore errors to ensure backwards compatibility
}
}
private static void ApplyBIOSInformation(this DeviceHardware deviceHardware)
{
try
{
using (var mSearcher = new ManagementObjectSearcher("SELECT BIOSVersion, Manufacturer, ReleaseDate, SerialNumber, SMBIOSBIOSVersion, SMBIOSMajorVersion, SMBIOSMinorVersion, SystemBiosMajorVersion, SystemBiosMinorVersion FROM Win32_BIOS WHERE PrimaryBios=true"))
{ {
using (var mResults = mSearcher.Get()) using (var mResults = mSearcher.Get())
{ {
@@ -61,11 +285,42 @@ namespace Disco.Client.Interop
{ {
var serialNumber = (string)mItem.GetPropertyValue("SerialNumber"); var serialNumber = (string)mItem.GetPropertyValue("SerialNumber");
if (!string.IsNullOrWhiteSpace(serialNumber)) if (!string.IsNullOrWhiteSpace(serialNumber))
{ deviceHardware.SerialNumber = serialNumber.Trim();
DeviceHardware.SerialNumber = serialNumber.Trim();
}
ErrorReporting.DeviceIdentifier = DeviceHardware.SerialNumber; var manufacturer = (string)mItem.GetPropertyValue("Manufacturer");
if (deviceHardware.Manufacturer == null && !string.IsNullOrWhiteSpace(manufacturer))
deviceHardware.Manufacturer = manufacturer.Trim();
ErrorReporting.DeviceIdentifier = deviceHardware.SerialNumber;
var biosVersion = (string[])mItem.GetPropertyValue("BIOSVersion");
var releaseDate = default(DateTime?);
var releaseDateString = (string)mItem.GetPropertyValue("ReleaseDate");
if (releaseDateString != null && releaseDateString.Length == 25 && int.TryParse(releaseDateString.Substring(22), out var offsetMinutes))
{
releaseDateString = $"{releaseDateString.Substring(0, 22)}{offsetMinutes / 60:00}:{Math.Abs(offsetMinutes % 60):00}";
if (DateTime.TryParseExact(releaseDateString, "yyyyMMddHHmmss.ffffffzzz", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var releaseDateResult))
releaseDate = releaseDateResult;
}
var sMBIOSBIOSVersion = (string)mItem.GetPropertyValue("SMBIOSBIOSVersion");
var sMBIOSMajorVersion = (ushort?)mItem.GetPropertyValue("SMBIOSMajorVersion");
var sMBIOSMinorVersion = (ushort?)mItem.GetPropertyValue("SMBIOSMinorVersion");
var systemBiosMajorVersion = (byte?)mItem.GetPropertyValue("SystemBiosMajorVersion");
var systemBiosMinorVersion = (byte?)mItem.GetPropertyValue("SystemBiosMinorVersion");
var bios = new Bios()
{
BIOSVersion = biosVersion,
Manufacturer = manufacturer,
ReleaseDate = releaseDate,
SerialNumber = serialNumber,
SMBIOSBIOSVersion = sMBIOSBIOSVersion,
SMBIOSMajorVersion = sMBIOSMajorVersion,
SMBIOSMinorVersion = sMBIOSMinorVersion,
SystemBiosMajorVersion = systemBiosMajorVersion,
SystemBiosMinorVersion = systemBiosMinorVersion,
};
deviceHardware.Bios = new List<Bios>() { bios };
} }
else else
{ {
@@ -77,15 +332,15 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve BIOS information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve BIOS information from WMI", ex);
} }
} }
private static void ApplySystemInformation(this DeviceHardware DeviceHardware) private static void ApplySystemInformation(this DeviceHardware deviceHardware)
{ {
try try
{ {
using (var mSearcher = new ManagementObjectSearcher("SELECT Manufacturer, Model, PartOfDomain, PCSystemType, Domain FROM Win32_ComputerSystem")) using (var mSearcher = new ManagementObjectSearcher("SELECT ChassisSKUNumber, CurrentTimeZone, Description, Manufacturer, Model, OEMStringArray, PCSystemType, PrimaryOwnerContact, PrimaryOwnerName, Roles, SystemSKUNumber, SystemType FROM Win32_ComputerSystem"))
{ {
using (var mResults = mSearcher.Get()) using (var mResults = mSearcher.Get())
{ {
@@ -95,17 +350,40 @@ namespace Disco.Client.Interop
{ {
var manufacturer = (string)mItem.GetPropertyValue("Manufacturer"); var manufacturer = (string)mItem.GetPropertyValue("Manufacturer");
if (!string.IsNullOrWhiteSpace(manufacturer)) if (!string.IsNullOrWhiteSpace(manufacturer))
{ deviceHardware.Manufacturer = manufacturer.Trim();
DeviceHardware.Manufacturer = manufacturer.Trim();
}
var model = (string)mItem.GetPropertyValue("Model"); var model = (string)mItem.GetPropertyValue("Model");
if (!string.IsNullOrWhiteSpace(model)) if (!string.IsNullOrWhiteSpace(model))
{ deviceHardware.Model = model;
DeviceHardware.Model = model.ToString();
}
DeviceHardware.ModelType = ((PCSystemTypes)mItem.GetPropertyValue("PCSystemType")).Description(); deviceHardware.ModelType = ((PCSystemTypes)mItem.GetPropertyValue("PCSystemType")).Description();
var chassisSKUNumber = (string)mItem.GetPropertyValue("ChassisSKUNumber");
var currentTimeZone = (short?)mItem.GetPropertyValue("CurrentTimeZone");
var description = (string)mItem.GetPropertyValue("Description");
var oemStringArray = (string[])mItem.GetPropertyValue("OEMStringArray");
var pcSystemType = ((PCSystemTypes)mItem.GetPropertyValue("PCSystemType")).ToString();
var primaryOwnerContact = (string)mItem.GetPropertyValue("PrimaryOwnerContact");
var primaryOwnerName = (string)mItem.GetPropertyValue("PrimaryOwnerName");
var roles = (string[])mItem.GetPropertyValue("Roles");
var systemSKUNumber = (string)mItem.GetPropertyValue("SystemSKUNumber");
var systemType = (string)mItem.GetPropertyValue("SystemType");
var computerSystem = new ComputerSystem()
{
ChassisSKUNumber = chassisSKUNumber,
CurrentTimeZone = currentTimeZone,
Description = description,
OEMStringArray = oemStringArray,
PCSystemType = pcSystemType,
PrimaryOwnerContact = primaryOwnerContact,
PrimaryOwnerName = primaryOwnerName,
Roles = roles,
SystemSKUNumber = systemSKUNumber,
SystemType = systemType,
};
deviceHardware.ComputerSystem = new List<ComputerSystem>() { computerSystem };
} }
else else
{ {
@@ -117,11 +395,11 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve ComputerSystem information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve ComputerSystem information from WMI", ex);
} }
} }
public static void ApplySystemInformation(this Enrol Enrol) public static void ApplySystemInformation(this Enrol enrol)
{ {
try try
{ {
@@ -133,11 +411,11 @@ namespace Disco.Client.Interop
{ {
if (mItem != null) if (mItem != null)
{ {
Enrol.IsPartOfDomain = (bool)mItem.GetPropertyValue("PartOfDomain"); enrol.IsPartOfDomain = (bool)mItem.GetPropertyValue("PartOfDomain");
if (Enrol.IsPartOfDomain) if (enrol.IsPartOfDomain)
{ {
Enrol.DNSDomainName = (string)mItem.GetPropertyValue("Domain"); enrol.DNSDomainName = (string)mItem.GetPropertyValue("Domain");
} }
} }
else else
@@ -150,22 +428,15 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve ComputerSystem information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve ComputerSystem information from WMI", ex);
} }
} }
private static void ApplyBaseBoardInformation(this DeviceHardware DeviceHardware) private static void ApplyBaseBoardInformation(this DeviceHardware deviceHardware)
{
// Added 2012-11-22 G# - Lenovo IdeaPad Serial SHIM
// http://www.discoict.com.au/forum/feature-requests/2012/11/serial-number-detection-on-ideapads.aspx
if (string.IsNullOrWhiteSpace(DeviceHardware.SerialNumber) ||
(DeviceHardware.Manufacturer.Equals("LENOVO", StringComparison.OrdinalIgnoreCase) &&
(DeviceHardware.Model.Equals("S10-3", StringComparison.OrdinalIgnoreCase) // S10-3
|| DeviceHardware.Model.Equals("2957", StringComparison.OrdinalIgnoreCase)))) // S10-2
{ {
try try
{ {
using (var mSearcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BaseBoard")) using (var mSearcher = new ManagementObjectSearcher("SELECT ConfigOptions, Manufacturer, Model, PartNumber, Product, SerialNumber, SKU, Version FROM Win32_BaseBoard"))
{ {
using (var mResults = mSearcher.Get()) using (var mResults = mSearcher.Get())
{ {
@@ -173,12 +444,56 @@ namespace Disco.Client.Interop
{ {
if (mItem != null) if (mItem != null)
{ {
var serialNumber = (string)mItem.GetPropertyValue("SerialNumber"); // Apply Manufacturer/Model information only if not previously found in Win32_ComputerSystem
if (!string.IsNullOrWhiteSpace(serialNumber)) var manufacturer = (string)mItem.GetPropertyValue("Manufacturer");
if (deviceHardware.Manufacturer == null && !string.IsNullOrWhiteSpace(manufacturer))
{ {
DeviceHardware.SerialNumber = serialNumber.Trim(); deviceHardware.Manufacturer = manufacturer.Trim();
ErrorReporting.DeviceIdentifier = DeviceHardware.SerialNumber;
} }
var model = (string)mItem.GetPropertyValue("Model");
if (deviceHardware.Model == null && !string.IsNullOrWhiteSpace(model))
{
deviceHardware.Model = model;
}
var product = (string)mItem.GetPropertyValue("Product");
if (deviceHardware.Model == null && !string.IsNullOrWhiteSpace(product))
{
deviceHardware.Model = product;
}
// Added 2012-11-22 G# - Lenovo IdeaPad Serial SHIM
// http://www.discoict.com.au/forum/feature-requests/2012/11/serial-number-detection-on-ideapads.aspx
var serialNumber = (string)mItem.GetPropertyValue("SerialNumber");
if (!string.IsNullOrWhiteSpace(serialNumber) &&
(deviceHardware.SerialNumber == null ||
((deviceHardware.Manufacturer?.Equals("LENOVO", StringComparison.OrdinalIgnoreCase) ?? false) &&
((deviceHardware.Model?.Equals("S10-3", StringComparison.OrdinalIgnoreCase) ?? false) // S10-3
|| (deviceHardware.Model?.Equals("2957", StringComparison.OrdinalIgnoreCase) ?? false)))))
{
deviceHardware.SerialNumber = serialNumber.Trim();
ErrorReporting.DeviceIdentifier = deviceHardware.SerialNumber;
}
var configOptions = (string[])mItem.GetPropertyValue("ConfigOptions");
var partNumber = (string)mItem.GetPropertyValue("PartNumber");
var sku = (string)mItem.GetPropertyValue("SKU");
var version = (string)mItem.GetPropertyValue("Version");
var baseBoard = new BaseBoard()
{
ConfigOptions = configOptions,
Manufacturer = manufacturer,
Model = model,
PartNumber = partNumber,
Product = product,
SerialNumber = serialNumber,
SKU = sku,
Version = version,
};
deviceHardware.BasebBoard = new List<BaseBoard>() { baseBoard };
} }
else else
{ {
@@ -190,12 +505,11 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve BaseBoard information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve BaseBoard information from WMI", ex);
}
} }
} }
private static void ApplySystemProductInformation(this DeviceHardware DeviceHardware) private static void ApplySystemProductInformation(this DeviceHardware deviceHardware)
{ {
try try
{ {
@@ -207,19 +521,30 @@ namespace Disco.Client.Interop
{ {
if (mItem != null) if (mItem != null)
{ {
if (DeviceHardware.SerialNumber == null) if (deviceHardware.SerialNumber == null)
{ {
var serialNumber = mItem.GetPropertyValue("IdentifyingNumber") as string; var serialNumber = mItem.GetPropertyValue("IdentifyingNumber") as string;
if (!string.IsNullOrWhiteSpace(serialNumber)) if (!string.IsNullOrWhiteSpace(serialNumber))
{ {
DeviceHardware.SerialNumber = serialNumber.Trim(); deviceHardware.SerialNumber = serialNumber.Trim();
ErrorReporting.DeviceIdentifier = DeviceHardware.SerialNumber; ErrorReporting.DeviceIdentifier = deviceHardware.SerialNumber;
} }
} }
var uUID = (string)mItem.GetPropertyValue("UUID"); var uUID = (string)mItem.GetPropertyValue("UUID");
if (!string.IsNullOrWhiteSpace(uUID)) if (!string.IsNullOrWhiteSpace(uUID))
DeviceHardware.UUID = uUID.Trim(); {
deviceHardware.UUID = uUID.Trim();
// if serial number is absent attempt using UUID if valid
if (string.IsNullOrWhiteSpace(deviceHardware.SerialNumber))
{
if (Guid.TryParse(deviceHardware.UUID, out var uuidGuid) && uuidGuid != Guid.Empty)
{
deviceHardware.SerialNumber = $"UUID{uuidGuid:N}";
}
}
}
} }
else else
{ {
@@ -231,10 +556,22 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve ComputerSystemProduct information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve ComputerSystemProduct information from WMI", ex);
} }
} }
private static void ApplyMobileDeviceManagementInformation(this DeviceHardware deviceHardware)
{
try
{
using (var wmiObject = new ManagementObject(@"\\.\ROOT\CIMV2\mdm\dmmap:MDM_DevDetail_Ext01.InstanceID=""Ext"",ParentID=""./DevDetail"""))
{
deviceHardware.MdmHardwareData = (string)wmiObject.GetPropertyValue("DeviceHardwareData");
}
}
catch (Exception) { }
}
private static string Description(this PCSystemTypes type) private static string Description(this PCSystemTypes type)
{ {
switch (type) switch (type)
@@ -273,5 +610,93 @@ namespace Disco.Client.Interop
PerformanceServer, PerformanceServer,
Maximum Maximum
} }
private enum ProcessorArchitectures : ushort
{
x86 = 0,
MIPS = 1,
Alpha = 2,
PowerPC = 3,
ia64 = 6,
x64 = 9,
}
private enum DiskLogicalDriveTypes : uint
{
Unknown = 0,
NoRootDirectory,
Removable,
Fixed,
Remote,
CDRom,
RAMDisk,
}
private enum DiskLogicalMediaTypes : uint
{
Unknown = 0,
F5_1Pt2_512,
F3_1Pt44_512,
F3_2Pt88_512,
F3_20Pt8_512,
F3_720_512,
F5_360_512,
F5_320_512,
F5_320_1024,
F5_180_512,
F5_160_512,
RemovableMedia,
FixedMedia,
F3_120M_512,
F3_640_512,
F5_640_512,
F5_720_512,
F3_1Pt2_512,
F3_1Pt23_1024,
F5_1Pt23_1024,
F3_128Mb_512,
F3_230Mb_512,
F8_256_128,
F3_200Mb_512,
F3_240M_512,
F3_32M_512,
}
private enum BatteryAvailability : ushort
{
Other = 1,
Unknown = 2,
RunningFullPower = 3,
Warning = 4,
InTest = 5,
NotApplicable = 6,
PowerOff = 7,
OffLine = 8,
OffDuty = 9,
Degraded = 10,
NotInstalled = 11,
InstallError = 12,
PowerSaveUnknown = 13,
PowerSaveLowPowerMode = 14,
PowerSaveStandby = 15,
PowerCycle = 16,
PowerSaveWarning = 17,
Paused = 18,
NotReady = 19,
NotConfigured = 20,
Quiesced = 21,
}
private enum BatteryChemistry : ushort
{
Other = 1,
Unknown = 2,
LeadAcid = 3,
NickelCadmium = 4,
NickelMetalHydride = 5,
LithiumIon = 6,
ZincAir = 7,
LithiumPolymer = 8,
}
} }
} }
@@ -1,9 +1,4 @@
using System; namespace Disco.Client.Interop.Native
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Disco.Client.Interop.Native
{ {
public enum NetworkConnectionStatuses : ushort public enum NetworkConnectionStatuses : ushort
{ {
+1 -1
View File
@@ -68,7 +68,7 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve NetworkAdapter information from WMI", ex); throw new Exception("Disco ICT Client was unable to retrieve NetworkAdapter information from WMI", ex);
} }
} }
+12 -26
View File
@@ -17,16 +17,13 @@ namespace Disco.Client.Interop
{ {
try try
{ {
IntPtr wlanHandle;
uint wlanServiceVersion;
if (WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out wlanServiceVersion, out wlanHandle) == WlanApi.ERROR_SUCCESS) if (WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out var wlanServiceVersion, out var wlanHandle) == WlanApi.ERROR_SUCCESS)
{ {
try try
{ {
IntPtr wlanInterfacesPtr;
if (WlanApi.WlanEnumInterfaces(wlanHandle, IntPtr.Zero, out wlanInterfacesPtr) == WlanApi.ERROR_SUCCESS) if (WlanApi.WlanEnumInterfaces(wlanHandle, IntPtr.Zero, out var wlanInterfacesPtr) == WlanApi.ERROR_SUCCESS)
{ {
try try
{ {
@@ -61,7 +58,7 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve Wireless NetworkAdapter information from WlanApi", ex); throw new Exception("Disco ICT Client was unable to retrieve Wireless NetworkAdapter information from WlanApi", ex);
} }
} }
@@ -69,12 +66,10 @@ namespace Disco.Client.Interop
{ {
try try
{ {
IntPtr wlanHandle;
uint wlanServiceVersion;
uint interopResult; uint interopResult;
// Connect to wireless service // Connect to wireless service
interopResult = WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out wlanServiceVersion, out wlanHandle); interopResult = WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out var wlanServiceVersion, out var wlanHandle);
if (interopResult == WlanApi.ERROR_SERVICE_NOT_ACTIVE) if (interopResult == WlanApi.ERROR_SERVICE_NOT_ACTIVE)
{ {
// Indicates the Wlan service has not been started on the client // Indicates the Wlan service has not been started on the client
@@ -102,17 +97,16 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to retrieve Wireless Profiles from WlanApi", ex); throw new Exception("Disco ICT Client was unable to retrieve Wireless Profiles from WlanApi", ex);
} }
} }
private static List<WirelessProfile> GetWirelessProfiles(IntPtr wlanHandle) private static List<WirelessProfile> GetWirelessProfiles(IntPtr wlanHandle)
{ {
uint interopResult; uint interopResult;
IntPtr wlanInterfacesPtr;
// Enumerate wireless interfaces // Enumerate wireless interfaces
interopResult = WlanApi.WlanEnumInterfaces(wlanHandle, IntPtr.Zero, out wlanInterfacesPtr); interopResult = WlanApi.WlanEnumInterfaces(wlanHandle, IntPtr.Zero, out var wlanInterfacesPtr);
if (interopResult != WlanApi.ERROR_SUCCESS) if (interopResult != WlanApi.ERROR_SUCCESS)
{ {
throw new Exception($"Unable to list interfaces with the local wireless service. WlanEnumInterfaces returned: {interopResult}"); throw new Exception($"Unable to list interfaces with the local wireless service. WlanEnumInterfaces returned: {interopResult}");
@@ -124,9 +118,8 @@ namespace Disco.Client.Interop
foreach (var wlanInterface in wlanInterfaces.InterfaceInfo) foreach (var wlanInterface in wlanInterfaces.InterfaceInfo)
{ {
IntPtr wlanProfilesPtr;
// Enumerate wireless profiles for interface // Enumerate wireless profiles for interface
interopResult = WlanApi.WlanGetProfileList(wlanHandle, wlanInterface.InterfaceGuid, IntPtr.Zero, out wlanProfilesPtr); interopResult = WlanApi.WlanGetProfileList(wlanHandle, wlanInterface.InterfaceGuid, IntPtr.Zero, out var wlanProfilesPtr);
if (interopResult != WlanApi.ERROR_SUCCESS) if (interopResult != WlanApi.ERROR_SUCCESS)
{ {
throw new Exception($"Unable to list wireless profiles for the {wlanInterface.InterfaceGuid} interface with the local wireless service. WlanGetProfileList returned: {interopResult}"); throw new Exception($"Unable to list wireless profiles for the {wlanInterface.InterfaceGuid} interface with the local wireless service. WlanGetProfileList returned: {interopResult}");
@@ -165,12 +158,10 @@ namespace Disco.Client.Interop
try try
{ {
IntPtr wlanHandle;
uint wlanServiceVersion;
uint interopResult; uint interopResult;
// Connect to wireless service // Connect to wireless service
interopResult = WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out wlanServiceVersion, out wlanHandle); interopResult = WlanApi.WlanOpenHandle(WlanApi.WLAN_API_VERSION_2_0, IntPtr.Zero, out var wlanServiceVersion, out var wlanHandle);
if (interopResult == WlanApi.ERROR_SERVICE_NOT_ACTIVE) if (interopResult == WlanApi.ERROR_SERVICE_NOT_ACTIVE)
{ {
// Indicates the Wlan service has not been started on the client // Indicates the Wlan service has not been started on the client
@@ -243,9 +234,8 @@ namespace Disco.Client.Interop
} }
else else
{ {
uint pdwReasonCode;
Presentation.UpdateStatus("Enrolling Device", $"Configuring Wireless Profiles\r\nAdding Wireless Profile '{addProfile.Name}' on '{adapter.NetConnectionID}'", true, -1, 1000); Presentation.UpdateStatus("Enrolling Device", $"Configuring Wireless Profiles\r\nAdding Wireless Profile '{addProfile.Name}' on '{adapter.NetConnectionID}'", true, -1, 1000);
interopResult = WlanApi.WlanSetProfile(wlanHandle, adapter.ConnectionIdentifier, 0, addProfile.ProfileXml, null, true, IntPtr.Zero, out pdwReasonCode); interopResult = WlanApi.WlanSetProfile(wlanHandle, adapter.ConnectionIdentifier, 0, addProfile.ProfileXml, null, true, IntPtr.Zero, out var pdwReasonCode);
if (interopResult != WlanApi.ERROR_SUCCESS) if (interopResult != WlanApi.ERROR_SUCCESS)
{ {
@@ -285,11 +275,8 @@ namespace Disco.Client.Interop
else else
{ {
// Load profile // Load profile
IntPtr pstrProfileXml;
uint pdwFlags;
IntPtr pdwGrantAccess;
interopResult = WlanApi.WlanGetProfile(wlanHandle, adapter.ConnectionIdentifier, profileName, IntPtr.Zero, out pstrProfileXml, out pdwFlags, out pdwGrantAccess); interopResult = WlanApi.WlanGetProfile(wlanHandle, adapter.ConnectionIdentifier, profileName, IntPtr.Zero, out var pstrProfileXml, out var pdwFlags, out var pdwGrantAccess);
if (interopResult == WlanApi.ERROR_SUCCESS) if (interopResult == WlanApi.ERROR_SUCCESS)
{ {
@@ -312,10 +299,9 @@ namespace Disco.Client.Interop
if (!XNode.DeepEquals(originalProfileXml, transformedProfileXml)) if (!XNode.DeepEquals(originalProfileXml, transformedProfileXml))
{ {
// Set Profile // Set Profile
uint pdwReasonCode;
Presentation.UpdateStatus("Enrolling Device", $"Configuring Wireless Profiles\r\nModifying Wireless Profile '{profileName}' on '{adapter.NetConnectionID}'", true, -1, 1000); Presentation.UpdateStatus("Enrolling Device", $"Configuring Wireless Profiles\r\nModifying Wireless Profile '{profileName}' on '{adapter.NetConnectionID}'", true, -1, 1000);
transformProfileXml = transformedProfileXml.ToString(SaveOptions.None); transformProfileXml = transformedProfileXml.ToString(SaveOptions.None);
interopResult = WlanApi.WlanSetProfile(wlanHandle, adapter.ConnectionIdentifier, 0, transformProfileXml, null, true, IntPtr.Zero, out pdwReasonCode); interopResult = WlanApi.WlanSetProfile(wlanHandle, adapter.ConnectionIdentifier, 0, transformProfileXml, null, true, IntPtr.Zero, out var pdwReasonCode);
if (interopResult != WlanApi.ERROR_SUCCESS) if (interopResult != WlanApi.ERROR_SUCCESS)
{ {
@@ -356,7 +342,7 @@ namespace Disco.Client.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("Disco Client was unable to apply Wireless Profile Changes using WlanApi", ex); throw new Exception("Disco ICT Client was unable to apply Wireless Profile Changes using WlanApi", ex);
} }
} }
+20 -10
View File
@@ -1,11 +1,10 @@
using System; using Disco.Client.Extensions;
using System.Collections.Generic; using Disco.Client.Interop;
using System.Linq; using System;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Disco.Client.Extensions;
using Disco.Client.Interop;
namespace Disco.Client namespace Disco.Client
{ {
@@ -22,13 +21,13 @@ namespace Disco.Client
} }
public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress, int TryDelay) public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress, int TryDelay)
{ {
Presentation.UpdateStatus(SubHeading, Message, ShowProgress, Progress); UpdateStatus(SubHeading, Message, ShowProgress, Progress);
if (TryDelay > 0) if (TryDelay > 0)
Presentation.TryDelay(TryDelay); Presentation.TryDelay(TryDelay);
} }
public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress) public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress)
{ {
Console.WriteLine("#{0},{1},{2},{3}", SubHeading.EscapeMessage(), Message.EscapeMessage(), ShowProgress.ToString(), Progress.ToString()); Console.WriteLine($"#{SubHeading.EscapeMessage()},{Message.EscapeMessage()},{ShowProgress},{Progress}");
} }
public static void TryDelay(int Milliseconds) public static void TryDelay(int Milliseconds)
{ {
@@ -40,6 +39,11 @@ namespace Disco.Client
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"); message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}");
message.Append($"Server: {Program.ServerUrl})");
if (Program.ServerUrl.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
message.AppendLine(" [Secure]");
else
message.AppendLine(" [Insecure]");
message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})"); message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})");
Console.ForegroundColor = ConsoleColor.Yellow; Console.ForegroundColor = ConsoleColor.Yellow;
UpdateStatus("Preparation Client Started", message.ToString(), false, 0); UpdateStatus("Preparation Client Started", message.ToString(), false, 0);
@@ -50,12 +54,18 @@ namespace Disco.Client
{ {
Console.ForegroundColor = ConsoleColor.Magenta; Console.ForegroundColor = ConsoleColor.Magenta;
ClientServiceException clientServiceException = ex as ClientServiceException; if (ex is ClientServiceException clientServiceException)
if (clientServiceException != null)
{ {
UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}", UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}",
clientServiceException.Message, false, 0); clientServiceException.Message, false, 0);
} }
else if (ex is WebException exWeb &&
exWeb.Response is HttpWebResponse webResponse &&
webResponse.StatusCode == HttpStatusCode.InternalServerError)
{
UpdateStatus("Something went wrong on the server",
"Review logs for more information (Configuration > Logging)", false, 0);
}
else else
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
@@ -92,7 +102,7 @@ namespace Disco.Client
public static void RegisterBootstrapperPostActions(ShutdownActions ShutdownAction, bool Uninstall) public static void RegisterBootstrapperPostActions(ShutdownActions ShutdownAction, bool Uninstall)
{ {
Console.WriteLine("!{0},{1}", Enum.GetName(typeof(ShutdownActions), ShutdownAction), Uninstall ? "UninstallBootstrapper" : "DontUninstallBootstrapper"); Console.WriteLine($"!{Enum.GetName(typeof(ShutdownActions), ShutdownAction)},{(Uninstall ? "UninstallBootstrapper" : "DontUninstallBootstrapper")}");
} }
public enum ShutdownActions public enum ShutdownActions
{ {
+88 -15
View File
@@ -1,11 +1,10 @@
using System; using Disco.Client.Extensions;
using System.Collections.Generic; using Disco.Client.Interop;
using Disco.Models.ClientServices;
using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks;
using Disco.Models.ClientServices;
using Disco.Client.Extensions;
namespace Disco.Client namespace Disco.Client
{ {
@@ -14,6 +13,9 @@ namespace Disco.Client
public static bool IsAuthenticated { get; set; } public static bool IsAuthenticated { get; set; }
public static bool RebootRequired { get; set; } public static bool RebootRequired { get; set; }
public static bool AllowUninstall { get; set; } public static bool AllowUninstall { get; set; }
public static int BootstrapperVersion { get; private set; } = 1;
public static int BootstrapperProcessId { get; private set; } = -1;
public static Uri ServerUrl { get; private set; }
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
@@ -27,28 +29,31 @@ namespace Disco.Client
{ {
Console.WriteLine("Waiting for Debugger to Attach"); Console.WriteLine("Waiting for Debugger to Attach");
System.Threading.Thread.Sleep(1000); System.Threading.Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached); } while (!Debugger.IsAttached);
} }
#endif #endif
// Initialize Environment Settings // Initialize Environment Settings
SetupEnvironment(); SetupEnvironment(args);
if (ServerUrl == null)
keepProcessing = DiscoverDiscoIct();
// Report to Bootstrapper // Report to Bootstrapper
Presentation.WriteBanner(); Presentation.WriteBanner();
// WhoAmI Phase // WhoAmI Phase
keepProcessing = Program.WhoAmI(); keepProcessing = WhoAmI();
// Enrol Phase // Enrol Phase
if (keepProcessing) if (keepProcessing)
keepProcessing = Program.Enrol(); keepProcessing = Enrol();
// End conversation with Bootstrapper // End conversation with Bootstrapper
Presentation.WriteFooter(Program.RebootRequired, Program.AllowUninstall, !keepProcessing); Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing);
} }
public static void SetupEnvironment() public static void SetupEnvironment(string[] args)
{ {
// Hookup Unhandled Error Handling // Hookup Unhandled Error Handling
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException;
@@ -57,21 +62,66 @@ namespace Disco.Client
WebRequest.DefaultWebProxy = new WebProxy(); WebRequest.DefaultWebProxy = new WebProxy();
// Override Http 100 Continue Behaviour // Override Http 100 Continue Behaviour
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// Assume success unless otherwise notified // Assume success unless otherwise notified
AllowUninstall = true; AllowUninstall = true;
if (args != null && args.Length == 3)
{
// Parse Bootstrapper Version
int parsedVersion;
if (int.TryParse(args[0], out parsedVersion))
BootstrapperVersion = parsedVersion;
// Parse Bootstrapper Process ID
int parsedProcessId;
if (int.TryParse(args[1], out parsedProcessId))
BootstrapperProcessId = parsedProcessId;
// Parse Server URL
Uri parsedUri;
if (Uri.TryCreate(args[2], UriKind.Absolute, out parsedUri))
ServerUrl = parsedUri;
}
else
{
BootstrapperVersion = 1;
BootstrapperProcessId = -1;
ServerUrl = null;
}
// Detect Disco.Bootstrapper - Create Enable UI Delay if Running // Detect Disco.Bootstrapper - Create Enable UI Delay if Running
Presentation.DelayUI = false;
try try
{ {
Presentation.DelayUI = (System.Diagnostics.Process.GetProcessesByName("Disco.ClientBootstrapper").Length > 0); if (BootstrapperProcessId != -1)
{
var parentProcess = Process.GetProcessById(BootstrapperProcessId);
Presentation.DelayUI = !parentProcess.HasExited;
}
} }
catch (Exception) catch (Exception)
{ {
Presentation.DelayUI = true; // Add Delays on Error
} }
} }
public static bool DiscoverDiscoIct()
{
try
{
Presentation.UpdateStatus("Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
Presentation.TryDelay(3000);
ServerUrl = EndpointDiscovery.DiscoverServer(null).Item1;
// Complete
return true;
}
catch (Exception ex)
{
ErrorReporting.ReportError(ex, false);
}
return false;
}
public static bool WhoAmI() public static bool WhoAmI()
{ {
try try
@@ -126,14 +176,37 @@ namespace Disco.Client
request = new Enrol(); request = new Enrol();
request.Build(); request.Build();
var startTime = DateTimeOffset.Now;
do
{
// Send Request // Send Request
Presentation.UpdateStatus("Enrolling Device", "Sending the enrolment request to the server.", true, -1); Presentation.UpdateStatus("Enrolling Device", "Sending the enrolment request to the server.", true, -1);
response = request.Post(Program.IsAuthenticated); response = request.Post(IsAuthenticated);
// Process Response // Process Response
Presentation.UpdateStatus("Enrolling Device", "Processing the enrolment response from the server.", true, -1); Presentation.UpdateStatus("Enrolling Device", "Processing the enrolment response from the server.", true, -1);
response.Process(); response.Process();
if (response.IsPending)
{
request.PendingSessionId = response.SessionId;
request.PendingAuthorization = response.PendingAuthorization;
// Session Pending
var totalSeconds = (response.PendingTimeout - startTime).TotalSeconds;
var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds;
var progress = (int)((secondsConsumed / totalSeconds) * 100);
Presentation.UpdateStatus($"Pending Device Enrolment Approval: {response.PendingIdentifier}", $"Server: {Program.ServerUrl}{Environment.NewLine}Reason: {response.PendingReason}", true, progress);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
}
else
{
// Session Complete
break;
}
} while (true);
// Complete // Complete
return true; return true;
} }
+3 -3
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.2.16320.1300")] [assembly: AssemblyVersion("2.5.25262.0000")]
[assembly: AssemblyFileVersion("2.2.16320.1300")] [assembly: AssemblyFileVersion("2.5.25262.0000")]
+2 -2
View File
@@ -1,9 +1,9 @@
@ECHO OFF @ECHO OFF
IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService
Disco.Client.exe Disco.Client.exe %1 %2 %3
EXIT /B 0 EXIT /B 0
:RunAsNetworkService :RunAsNetworkService
ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1 ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1
PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat" PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat %1 %2 %3"
EXIT /B 0 EXIT /B 0
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="6.0.3" targetFramework="net40-Client" /> <package id="Newtonsoft.Json" version="13.0.2" targetFramework="net40-client" />
</packages> </packages>
+95 -106
View File
@@ -1,146 +1,122 @@
using System; using Disco.Client.Interop;
using Disco.ClientBootstrapper.Interop;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Net; using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
class BootstrapperLoop internal class BootstrapperLoop
{ {
private readonly Func<CancellationToken, Task> completeCallback;
public Thread LoopThread; private readonly CancellationToken cancellationToken;
public delegate void LoopCompleteCallback(); private readonly IStatus statusUI;
private LoopCompleteCallback mLoopCompleteCallback; private readonly Uri forcedServerUrl;
private IStatus statusUI;
private string tempWorkingDirectory; private string tempWorkingDirectory;
private StringBuilder errorMessage;
private Process clientProcess; private Process clientProcess;
#if DEBUG public BootstrapperLoop(IStatus statusUI, Uri forcedServerUrl, Func<CancellationToken, Task> callback, CancellationToken cancellationToken)
public const string DiscoServerName = "WS-GSHARP";
public const int DiscoServerPort = 57252;
#else
public const string DiscoServerName = "DISCO";
public const int DiscoServerPort = 9292;
#endif
public BootstrapperLoop(IStatus StatusUI, LoopCompleteCallback Callback)
{ {
this.statusUI = StatusUI; this.statusUI = statusUI;
this.mLoopCompleteCallback = Callback; this.forcedServerUrl = forcedServerUrl;
this.errorMessage = new StringBuilder(); completeCallback = callback;
this.cancellationToken = cancellationToken;
} }
public void Start() public void Start()
{ {
this.LoopThread = new Thread(new ThreadStart(loopHost)); Task.Factory.StartNew(async () =>
this.LoopThread.Start(); {
await Loop(forcedServerUrl, cancellationToken);
}, cancellationToken);
} }
private void loopHost() private async Task Loop(Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
try try
{ {
loop();
}
catch (Exception ex)
{
if (ex.GetType() == typeof(ThreadAbortException))
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
Program.WriteAppError(ex);
throw;
}
}
private void loop()
{
#if Debug
statusUI.UpdateStatus("Waiting for Debugger", "Please wait...", true, -1);
try
{
do
{
System.Threading.Thread.Sleep(10);
} while (!System.Diagnostics.Debugger.IsAttached);
}
catch (Exception ex)
{
statusUI.UpdateStatus("Error", ex.Message, true, -1);
return;
}
#else
statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1); statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
#endif
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco\\Temp"); tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), @"Disco\Temp");
if (!Directory.Exists(tempWorkingDirectory)) if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory); Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity // Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1); statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!Interop.NetworkInterop.PingDisco(DiscoServerName)) if (!NetworkInterop.HasNetworkConnectivity())
{ {
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1); statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo(); statusUI_WriteAdapterInfo();
if (!Interop.NetworkInterop.PingDisco(DiscoServerName)) if (!NetworkInterop.HasNetworkConnectivity())
{ {
// Check for Wireless // Check for Wireless
var hasWireless = (Interop.NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0); var hasWireless = (NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless) if (hasWireless)
{ {
// True: Do wireless loop // True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1); statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
Interop.NetworkInterop.ConfigureWireless(); await NetworkInterop.ConfigureWireless(cancellationToken);
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0); statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 100; i++) for (int i = 0; i < 30; i++)
{ {
statusUI_WriteAdapterInfo(); statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i); statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false); await Program.SleepThread(2000, false, cancellationToken);
if (Interop.NetworkInterop.PingDisco(DiscoServerName)) if (NetworkInterop.HasNetworkConnectivity())
break; break;
} }
if (!Interop.NetworkInterop.PingDisco(DiscoServerName)) if (!NetworkInterop.HasNetworkConnectivity())
{ {
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false); statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
Program.SleepThread(3000, false); await Program.SleepThread(3000, false, cancellationToken);
} }
} }
if (!Interop.NetworkInterop.PingDisco(DiscoServerName)) if (!NetworkInterop.HasNetworkConnectivity())
{ {
// Instruct user to connect network cable // Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null); statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 100; i++) for (int i = 0; i < 30; i++)
{ {
statusUI_WriteAdapterInfo(); statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i); statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false); await Program.SleepThread(2000, false, cancellationToken);
if (Interop.NetworkInterop.PingDisco(DiscoServerName)) if (NetworkInterop.HasNetworkConnectivity())
break; break;
} }
} }
} }
if (!Interop.NetworkInterop.PingDisco(DiscoServerName)) if (!NetworkInterop.HasNetworkConnectivity())
{ {
// Client Failed // Client Failed
if (this.mLoopCompleteCallback != null) if (completeCallback != null)
{ await completeCallback(cancellationToken);
this.mLoopCompleteCallback.BeginInvoke(null, null);
}
return; return;
} }
} }
Tuple<Uri, string> serverDiscovery;
statusUI.UpdateStatus(null, "Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
try
{
serverDiscovery = EndpointDiscovery.DiscoverServer(forcedServerUrl);
statusUI.UpdateStatus(null, null, $"{serverDiscovery.Item1} ({serverDiscovery.Item2})", true, -1);
}
catch (Exception)
{
statusUI.UpdateStatus(null, null, "Failed to locate Disco ICT Server, exiting...", true, -1);
await Program.SleepThread(2000, false, cancellationToken);
throw;
}
// Download Client // Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1); statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip"); string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
@@ -148,8 +124,29 @@ namespace Disco.ClientBootstrapper
{ {
// Don't use a proxy when downloading the Client // Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy(); webClient.Proxy = new WebProxy();
webClient.Headers.Add("X-DiscoICT-Discovery", serverDiscovery.Item2);
webClient.DownloadFile($"http://{DiscoServerName}:{DiscoServerPort}/Services/Client/PreparationClient", clientSourceLocation); try
{
webClient.DownloadFile(new Uri(serverDiscovery.Item1, "/Services/Client/PreparationClient"), clientSourceLocation);
}
catch (WebException ex)
{
if (ex.Response != null &&
ex.Response is HttpWebResponse response)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
statusUI.UpdateStatus(null, "Download failed: Bad Request", response.StatusDescription, true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
statusUI.UpdateStatus(null, "Download failed: Something went wrong on the server", "Review logs for more information (Configuration > Logging)", true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
}
throw;
}
} }
// Unzip Client // Unzip Client
@@ -166,7 +163,7 @@ namespace Disco.ClientBootstrapper
// Launch Client // Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1); statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat")) ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"), $"2 {Process.GetCurrentProcess().Id} {serverDiscovery.Item1}")
{ {
WorkingDirectory = clientLocation, WorkingDirectory = clientLocation,
CreateNoWindow = true, CreateNoWindow = true,
@@ -194,45 +191,47 @@ namespace Disco.ClientBootstrapper
// Cleanup // Cleanup
if (Directory.Exists(tempWorkingDirectory)) if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true); Directory.Delete(tempWorkingDirectory, true);
Interop.CertificateInterop.RemoveTempCerts(); CertificateInterop.RemoveTempCerts();
// Pause if Error
if (this.errorMessage.Length > 0)
{
Program.SleepThread(10000, true);
} }
catch (Exception ex)
{
if (ex.GetType() == typeof(ThreadAbortException))
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
if (ex.GetType() == typeof(OperationCanceledException))
return;
Program.WriteAppError(ex);
}
// End Of Loop // End Of Loop
if (this.mLoopCompleteCallback != null) if (completeCallback != null)
{ await completeCallback(cancellationToken);
this.mLoopCompleteCallback.BeginInvoke(null, null);
}
} }
void statusUI_WriteAdapterInfo() private void statusUI_WriteAdapterInfo()
{ {
var info = new StringBuilder(); var info = new StringBuilder();
foreach (var na in Interop.NetworkInterop.NetworkAdapters) foreach (var na in NetworkInterop.NetworkAdapters)
{ {
if (na.IsWireless) if (na.IsWireless)
{ {
info.AppendLine(string.Format("{0}: {1}", na.NetConnectionID, na.WirelessConnectionStatusMeaning(na.WirelessConnectionStatus))); info.AppendLine($"{na.NetConnectionID}: {na.WirelessConnectionStatusMeaning(na.WirelessConnectionStatus)}");
} }
else else
{ {
info.AppendLine(string.Format("{0}: {1}", na.NetConnectionID, na.ConnectionStatusMeaning(na.ConnectionStatus))); info.AppendLine($"{na.NetConnectionID}: {na.ConnectionStatusMeaning(na.ConnectionStatus)}");
} }
} }
statusUI.UpdateStatus(null, null, info.ToString()); statusUI.UpdateStatus(null, null, info.ToString());
} }
void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) private void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{ {
if (!string.IsNullOrWhiteSpace(e.Data)) if (!string.IsNullOrWhiteSpace(e.Data))
{ {
System.Diagnostics.Debug.WriteLine(string.Format("OUTPUT: {0}", e.Data));
var data = e.Data.Substring(1).Split(new char[] { ',' }); var data = e.Data.Substring(1).Split(new char[] { ',' });
switch (e.Data[0]) switch (e.Data[0])
{ {
@@ -249,15 +248,5 @@ namespace Disco.ClientBootstrapper
} }
} }
//void clientProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
//{
// if (!string.IsNullOrEmpty(e.Data))
// {
// System.Diagnostics.Debug.WriteLine(string.Format("ERROR: {0}", e.Data));
// this.errorMessage.AppendLine(e.Data);
// statusUI.UpdateStatus(null, "An Error Occurred", this.errorMessage.ToString(), false);
// }
//}
} }
} }
@@ -8,4 +8,4 @@ BootstrapperLocation = Mid(WScript.ScriptFullName, 1, InStrRev(WScript.ScriptFul
Call objShell.Run("""" & BootstrapperLocation & """ /Install", , True) Call objShell.Run("""" & BootstrapperLocation & """ /Install", , True)
WScript.Echo "Disco Client Bootstrapper Installed" WScript.Echo "Disco ICT Client Bootstrapper Installed"
@@ -10,8 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco.ClientBootstrapper</RootNamespace> <RootNamespace>Disco.ClientBootstrapper</RootNamespace>
<AssemblyName>Disco.ClientBootstrapper</AssemblyName> <AssemblyName>Disco.ClientBootstrapper</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<Install>true</Install> <Install>true</Install>
@@ -91,6 +90,9 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Disco.Client\Interop\EndpointDiscovery.cs">
<Link>Interop\EndpointDiscovery.cs</Link>
</Compile>
<Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs"> <Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs">
<Link>DotNetZip\BZip2\BitWriter.cs</Link> <Link>DotNetZip\BZip2\BitWriter.cs</Link>
</Compile> </Compile>
+2 -2
View File
@@ -85,7 +85,7 @@
this.labelVersion.Name = "labelVersion"; this.labelVersion.Name = "labelVersion";
this.labelVersion.Size = new System.Drawing.Size(167, 20); this.labelVersion.Size = new System.Drawing.Size(167, 20);
this.labelVersion.TabIndex = 0; this.labelVersion.TabIndex = 0;
this.labelVersion.Text = "Disco Bootstrapper v"; this.labelVersion.Text = "Disco ICT Bootstrapper v";
this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight;
// //
// FormStatus // FormStatus
@@ -106,7 +106,7 @@
this.ShowIcon = false; this.ShowIcon = false;
this.ShowInTaskbar = false; this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Disco - Client Bootstrapper"; this.Text = "Disco ICT - Client Bootstrapper";
this.TopMost = true; this.TopMost = true;
this.TransparencyKey = System.Drawing.Color.Magenta; this.TransparencyKey = System.Drawing.Color.Magenta;
this.ResumeLayout(false); this.ResumeLayout(false);
+20 -26
View File
@@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
@@ -12,69 +6,69 @@ namespace Disco.ClientBootstrapper
public partial class FormStatus : Form, IStatus public partial class FormStatus : Form, IStatus
{ {
private delegate void dUpdateStatus(string Heading, string SubHeading, string Message, Nullable<bool> ShowProgress, Nullable<int> Progress); private delegate void dUpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress);
private dUpdateStatus mUpdateStatus; private readonly dUpdateStatus mUpdateStatus;
public FormStatus() public FormStatus()
{ {
InitializeComponent(); InitializeComponent();
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
this.labelVersion.Text = string.Format("v{0}", version.ToString(3)); labelVersion.Text = $"v{version.ToString(3)}";
this.FormClosed += new FormClosedEventHandler(FormStatus_FormClosed); FormClosed += new FormClosedEventHandler(FormStatus_FormClosed);
mUpdateStatus = new dUpdateStatus(UpdateStatusDo); mUpdateStatus = new dUpdateStatus(UpdateStatusDo);
Cursor.Hide(); Cursor.Hide();
} }
void FormStatus_FormClosed(object sender, FormClosedEventArgs e) private void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
{ {
Cursor.Show(); Cursor.Show();
Program.ExitApplication(); Program.ExitApplication();
} }
public void UpdateStatus(string Heading, string SubHeading, string Message, Nullable<bool> ShowProgress = null, Nullable<int> Progress = null) public void UpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress = null, int? Progress = null)
{ {
try try
{ {
this.Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress); Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress);
} }
catch (Exception) { } catch (Exception) { }
} }
private void UpdateStatusDo(string Heading, string SubHeading, string Message, Nullable<bool> ShowProgress, Nullable<int> Progress) private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress)
{ {
if (Heading != null) if (Heading != null)
if (this.labelHeading.Text != Heading) if (labelHeading.Text != Heading)
this.labelHeading.Text = Heading; labelHeading.Text = Heading;
if (SubHeading != null) if (SubHeading != null)
if (this.labelSubHeading.Text != SubHeading) if (labelSubHeading.Text != SubHeading)
this.labelSubHeading.Text = SubHeading; labelSubHeading.Text = SubHeading;
if (Message != null) if (Message != null)
if (this.labelMessage.Text != Message) if (labelMessage.Text != Message)
this.labelMessage.Text = Message; labelMessage.Text = Message;
if (ShowProgress.HasValue) if (ShowProgress.HasValue)
{ {
if (ShowProgress.Value) if (ShowProgress.Value)
{ {
this.progressBar.Visible = true; progressBar.Visible = true;
if (Progress.HasValue) if (Progress.HasValue)
{ {
if (Progress.Value > 0) if (Progress.Value >= 0)
{ {
this.progressBar.Value = Math.Min(Progress.Value, 100); progressBar.Value = Math.Min(Progress.Value, 100);
this.progressBar.Style = ProgressBarStyle.Continuous; progressBar.Style = ProgressBarStyle.Continuous;
} }
else else
{ {
this.progressBar.Style = ProgressBarStyle.Marquee; progressBar.Style = ProgressBarStyle.Marquee;
} }
} }
} }
else else
{ {
this.progressBar.Visible = false; progressBar.Visible = false;
} }
} }
} }
+2 -7
View File
@@ -1,12 +1,7 @@
using System; namespace Disco.ClientBootstrapper
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Disco.ClientBootstrapper
{ {
interface IStatus interface IStatus
{ {
void UpdateStatus(string Heading, string SubHeading, string Message, Nullable<bool> ShowProgress = null, Nullable<int> Progress = null); void UpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress = null, int? Progress = null);
} }
} }
+22 -27
View File
@@ -1,43 +1,36 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
class InstallLoop internal class InstallLoop
{ {
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly string installLocation;
private readonly string wimImageId;
private readonly string tempPath;
private readonly Action completeCallback;
private readonly Uri forcedServerUrl;
public Thread LoopThread; public InstallLoop(string installLocation, string wimImageId, string tempPath, Action completeCallback, Uri forcedServerUrl)
public delegate void CompleteCallback();
private CompleteCallback mCompleteCallback;
private string InstallLocation;
private string WimImageId;
private string TempPath;
public InstallLoop(string InstallLocation, string WimImageId, string TempPath)
{ {
this.InstallLocation = InstallLocation; this.installLocation = installLocation;
this.WimImageId = WimImageId; this.wimImageId = wimImageId;
this.TempPath = TempPath; this.tempPath = tempPath;
this.completeCallback = completeCallback;
this.forcedServerUrl = forcedServerUrl;
} }
public void Start(CompleteCallback Callback) public void Start()
{ {
this.mCompleteCallback = Callback; var cancellationToken = cancellationTokenSource.Token;
this.LoopThread = new Thread(new ThreadStart(loopHost)); Task.Run(async () =>
this.LoopThread.Start();
}
private void loopHost()
{ {
try try
{ {
await Interop.InstallInterop.Install(installLocation, wimImageId, tempPath, forcedServerUrl, cancellationToken);
//Program.Status.UpdateStatus(null, null, "Testing UI"); completeCallback?.BeginInvoke(null, null);
//Program.SleepThread(5000, false);
Interop.InstallInterop.Install(this.InstallLocation, this.WimImageId, this.TempPath);
if (this.mCompleteCallback != null)
{
this.mCompleteCallback.BeginInvoke(null, null);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -45,10 +38,12 @@ namespace Disco.ClientBootstrapper
return; return;
if (ex.GetType() == typeof(ThreadInterruptedException)) if (ex.GetType() == typeof(ThreadInterruptedException))
return; return;
if (ex.GetType() == typeof(OperationCanceledException))
return;
Program.WriteAppError(ex); Program.WriteAppError(ex);
throw; throw;
} }
}, cancellationToken);
} }
} }
} }
@@ -1,10 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.IO; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
@@ -16,16 +17,17 @@ namespace Disco.ClientBootstrapper.Interop
if (_tempCerts != null && _tempCerts.Count > 0) if (_tempCerts != null && _tempCerts.Count > 0)
{ {
Remove(StoreName.My, StoreLocation.LocalMachine, _tempCerts); Remove(StoreName.My, StoreLocation.LocalMachine, _tempCerts);
Remove(StoreName.CertificateAuthority, StoreLocation.LocalMachine, _tempCerts); // dont remove root/intermediate certs as they may be have installed by client
Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts); //Remove(StoreName.CertificateAuthority, StoreLocation.LocalMachine, _tempCerts);
//Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts);
} }
} }
public static void AddTempCerts() public static async Task AddTempCerts(CancellationToken cancellationToken)
{ {
if (_tempCerts == null) if (_tempCerts == null)
_tempCerts = new List<string>(); _tempCerts = new List<string>();
var inlineCertificateLocation = Program.InlinePath.Value; var inlineCertificateLocation = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// Root Certificates // Root Certificates
try try
@@ -35,14 +37,15 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert); var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert);
if (result) if (result)
{ {
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, string.Format("Added Root Certificate: {0}", cert.ShortSubjectName())); Program.Status.UpdateStatus(null, null, $"Added Root Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -60,14 +63,15 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert); var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert);
if (result) if (result)
{ {
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, string.Format("Added Intermediate Certificate: {0}", cert.ShortSubjectName())); Program.Status.UpdateStatus(null, null, $"Added Intermediate Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -85,14 +89,15 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
var result = Add(StoreName.My, StoreLocation.LocalMachine, cert); var result = Add(StoreName.My, StoreLocation.LocalMachine, cert);
if (result) if (result)
{ {
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, string.Format("Added Host Certificate: {0}", cert.ShortSubjectName())); Program.Status.UpdateStatus(null, null, $"Added Host Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
} }
} }
@@ -106,7 +111,26 @@ namespace Disco.ClientBootstrapper.Interop
public static string ShortSubjectName(this X509Certificate2 Certificate) public static string ShortSubjectName(this X509Certificate2 Certificate)
{ {
string s = Certificate.Subject; string s = Certificate.Subject;
return s.Substring(s.IndexOf("=") + 1, s.IndexOf(",") - s.IndexOf("=") - 1); if (string.IsNullOrWhiteSpace(s))
{
return $"Unknown Certificate: {Certificate.Thumbprint}";
}
else
{
if (s.Length > 3 && s.StartsWith("CN=", StringComparison.OrdinalIgnoreCase))
{
var nameLength = s.IndexOf(',') - 3;
if (nameLength > 0)
{
return s.Substring(3, nameLength);
}
else
{
return s.Substring(3);
}
}
return s;
}
} }
public static bool Add(StoreName StoreName, StoreLocation StoreLocation, X509Certificate2 Certificate) public static bool Add(StoreName StoreName, StoreLocation StoreLocation, X509Certificate2 Certificate)
@@ -4,15 +4,17 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
public static class InstallInterop public static class InstallInterop
{ {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
[Flags] [Flags]
enum MoveFileFlags private enum MoveFileFlags
{ {
MOVEFILE_REPLACE_EXISTING = 0x00000001, MOVEFILE_REPLACE_EXISTING = 0x00000001,
MOVEFILE_COPY_ALLOWED = 0x00000002, MOVEFILE_COPY_ALLOWED = 0x00000002,
@@ -22,19 +24,19 @@ namespace Disco.ClientBootstrapper.Interop
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
} }
private static void Install(string RootFilesystemLocation, RegistryKey RootRegistryLocation, string FilesystemInstallLocation, string VirtualRootFilesystemLocation) private static async Task Install(string rootFilesystemLocation, RegistryKey rootRegistryLocation, string filesystemInstallLocation, string virtualRootFilesystemLocation, Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var InstallLocation = Path.Combine(RootFilesystemLocation, FilesystemInstallLocation); var InstallLocation = Path.Combine(rootFilesystemLocation, filesystemInstallLocation);
var BootstrapperCmdLinePath = Path.Combine(VirtualRootFilesystemLocation, FilesystemInstallLocation, "Disco.ClientBootstrapper.exe"); var BootstrapperCmdLinePath = Path.Combine(virtualRootFilesystemLocation, filesystemInstallLocation, "Disco.ClientBootstrapper.exe");
var GroupPolicyScriptsIniLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\scripts.ini"); var GroupPolicyScriptsIniLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\scripts.ini");
var GroupPolicyScriptsIniBackupLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); var GroupPolicyScriptsIniBackupLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\disco_scripts.ini");
// Create file system Location // Create file system Location
#region "Create File System Location" #region "Create File System Location"
Program.Status.UpdateStatus(null, null, "Creating Installation Location"); Program.Status.UpdateStatus(null, null, "Creating Installation Location");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
if (Directory.Exists(InstallLocation)) if (Directory.Exists(InstallLocation))
{ {
// Try and Delete Directory // Try and Delete Directory
@@ -44,7 +46,7 @@ namespace Disco.ClientBootstrapper.Interop
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new IOException(string.Format("Unable to delete folder: ", InstallLocation), ex); throw new IOException($"Unable to delete folder: {InstallLocation}", ex);
} }
} }
if (!Directory.Exists(InstallLocation)) if (!Directory.Exists(InstallLocation))
@@ -52,19 +54,23 @@ namespace Disco.ClientBootstrapper.Interop
var installDir = Directory.CreateDirectory(InstallLocation); var installDir = Directory.CreateDirectory(InstallLocation);
installDir.Attributes = installDir.Attributes | FileAttributes.Hidden; installDir.Attributes = installDir.Attributes | FileAttributes.Hidden;
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Copy files to file system location // Copy files to file system location
#region "Copy to File System" #region "Copy to File System"
Program.Status.UpdateStatus(null, null, "Copying Files"); Program.Status.UpdateStatus(null, null, "Copying Files");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Copy Bootstrapper // Copy Bootstrapper
// ie: Executing Assembly // ie: Executing Assembly
File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe")); File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe"));
cancellationToken.ThrowIfCancellationRequested();
foreach (var file in Directory.EnumerateFiles(SourceLocation)) foreach (var file in Directory.EnumerateFiles(SourceLocation))
{ {
cancellationToken.ThrowIfCancellationRequested();
var fileName = Path.GetFileName(file); var fileName = Path.GetFileName(file);
// Only Copy Certain Files // Only Copy Certain Files
@@ -86,7 +92,7 @@ namespace Disco.ClientBootstrapper.Interop
// Backup & Create Group Policy Scripts.ini // Backup & Create Group Policy Scripts.ini
#region "Group Policy Scripts.ini" #region "Group Policy Scripts.ini"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup // Backup
if (!File.Exists(GroupPolicyScriptsIniBackupLocation)) if (!File.Exists(GroupPolicyScriptsIniBackupLocation))
{ {
@@ -95,6 +101,7 @@ namespace Disco.ClientBootstrapper.Interop
File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation); File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation);
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create // Create
if (File.Exists(GroupPolicyScriptsIniLocation)) if (File.Exists(GroupPolicyScriptsIniLocation))
@@ -105,56 +112,67 @@ namespace Disco.ClientBootstrapper.Interop
{ {
using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode)) using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode))
{ {
scriptsIniStreamWriter.Write(string.Format("[Startup]{0}0CmdLine={1}{0}0Parameters=/AllowUninstall", Environment.NewLine, BootstrapperCmdLinePath)); scriptsIniStreamWriter.WriteLine("[Startup]");
scriptsIniStreamWriter.Flush(); scriptsIniStreamWriter.WriteLine($"0CmdLine={BootstrapperCmdLinePath}");
if (forcedServerUrl == null)
scriptsIniStreamWriter.WriteLine("0Parameters=/AllowUninstall");
else
scriptsIniStreamWriter.WriteLine($"0Parameters=/AllowUninstall {forcedServerUrl}");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Backup & Create Group Policy Registry // Backup & Create Group Policy Registry
#region "Group Policy Registry" #region "Group Policy Registry"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup Scripts // Backup Scripts
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts // Create Scripts
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown").Dispose(); rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown").Dispose();
using (var regScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup\\0")) using (var regScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup\0"))
{ {
regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine"), RegistryValueKind.String); regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine"), RegistryValueKind.String);
regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord); regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord);
using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0")) using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0"))
{ {
regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
if (forcedServerUrl == null)
regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord); regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord);
regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
} }
} }
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown").Dispose(); rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown").Dispose();
cancellationToken.ThrowIfCancellationRequested();
// Backup Scripts State // Backup Scripts State
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts State // Create Scripts State
using (var regStateScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup\\0")) using (var regStateScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup\0"))
{ {
regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
@@ -165,17 +183,21 @@ namespace Disco.ClientBootstrapper.Interop
using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0")) using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0"))
{ {
regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
if (forcedServerUrl == null)
regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regStateScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
} }
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Set Registry Startup Environment Policies // Set Registry Startup Environment Policies
#region "Registry Startup Policies" #region "Registry Startup Policies"
Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
using (var regWinlogon = RootRegistryLocation.OpenSubKey("Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) using (var regWinlogon = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord); regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord);
regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord); regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord);
@@ -183,96 +205,112 @@ namespace Disco.ClientBootstrapper.Interop
#endregion #endregion
} }
public static void Install(string InstallLocation, string WimImageId, string TempPath) public static async Task Install(string installLocation, string wimImageId, string tempPath, Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false); Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false);
if (string.IsNullOrWhiteSpace(InstallLocation)) if (string.IsNullOrWhiteSpace(installLocation))
InstallLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco"); installLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco");
if (InstallLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase)) cancellationToken.ThrowIfCancellationRequested();
if (installLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase))
{ {
// Offline File System (WIM) // Offline File System (WIM)
Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", string.Format("Install Location: {0}", InstallLocation)); Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {installLocation}");
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
// Mount WIM // Mount WIM
int wimImageIndex = 0; int wimImageIndex = 0;
using (var wim = new Interop.WIMInterop.WindowsImageContainer(InstallLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write)) using (var wim = new WIMInterop.WindowsImageContainer(installLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write))
{ {
if (WimImageId == null) cancellationToken.ThrowIfCancellationRequested();
WimImageId = "1"; if (wimImageId == null)
if (!int.TryParse(WimImageId, out wimImageIndex)) wimImageId = "1";
if (!int.TryParse(wimImageId, out wimImageIndex))
{ {
Program.Status.UpdateStatus(null, "Analysing WIM", string.Format("Looking for Image Name: {0}", WimImageId)); Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {wimImageId}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
for (int i = 0; i < wim.ImageCount; i++) for (int i = 0; i < wim.ImageCount; i++)
{ {
var wimImageInfo = new System.Xml.XmlDocument(); var wimImageInfo = new System.Xml.XmlDocument();
using (var wimImage = wim[i]) using (var wimImage = wim[i])
wimImageInfo.LoadXml(wimImage.ImageInformation); wimImageInfo.LoadXml(wimImage.ImageInformation);
var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME"); var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME");
if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(WimImageId, StringComparison.OrdinalIgnoreCase)) if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(wimImageId, StringComparison.OrdinalIgnoreCase))
{ {
wimImageIndex = i + 1; wimImageIndex = i + 1;
Program.Status.UpdateStatus(null, "Analysing WIM", string.Format("Found Image Id '{0}' at Index {1}", WimImageId, wimImageIndex)); Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{wimImageId}' at Index {wimImageIndex}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
break; break;
} }
} }
} }
} }
cancellationToken.ThrowIfCancellationRequested();
if (wimImageIndex == 0) if (wimImageIndex == 0)
{ {
Program.Status.UpdateStatus(null, "Error", string.Format("Unable to load WIM Image Id: {0}", WimImageId)); Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {wimImageId}");
Program.SleepThread(5000, false); await Program.SleepThread(5000, false, cancellationToken);
return; return;
} }
// Get Temp Path // Get Temp Path
var wimMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount"); var wimMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount");
if (Directory.Exists(wimMountPath)) if (Directory.Exists(wimMountPath))
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
Directory.CreateDirectory(wimMountPath); Directory.CreateDirectory(wimMountPath);
var wimTempMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount"); cancellationToken.ThrowIfCancellationRequested();
var wimTempMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount");
if (Directory.Exists(wimTempMountPath)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
Directory.CreateDirectory(wimTempMountPath); Directory.CreateDirectory(wimTempMountPath);
cancellationToken.ThrowIfCancellationRequested();
bool wimCommitChanges = true; bool wimCommitChanges = true;
Interop.WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null; WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null;
try try
{ {
// Mount WIM // Mount WIM
Program.Status.UpdateStatus(null, "Mounting WIM", string.Format("Mounting WIM Image to '{0}'", wimMountPath)); Program.Status.UpdateStatus(null, "Mounting WIM", $"Mounting WIM Image to '{wimMountPath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
m_MessageCallback = new Interop.WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump); m_MessageCallback = new WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump);
Interop.WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback); WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback);
Interop.WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, InstallLocation, wimImageIndex, wimTempMountPath); WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, installLocation, wimImageIndex, wimTempMountPath);
// Load Local Machine Registry // Load Local Machine Registry
var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE"); var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE");
Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", string.Format("Mounting Offline Registry Hive at '{0}'", wimHivePath)); Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", $"Mounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
using (var wimReg = new Interop.RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath)) using (var wimReg = new RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath))
{ {
try
{
cancellationToken.ThrowIfCancellationRequested();
using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true)) using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true))
{ {
string rootFileSystemLocation = wimMountPath; string rootFileSystemLocation = wimMountPath;
string fileSystemInstallLocation = "Disco"; string fileSystemInstallLocation = "Disco";
string virtualRootFileSystemLocation = "C:\\"; string virtualRootFileSystemLocation = "C:\\";
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation); cancellationToken.ThrowIfCancellationRequested();
await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation, forcedServerUrl, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
} }
}
finally
{
// Unload Local Machine Registry // Unload Local Machine Registry
Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", string.Format("Unmounting Offline Registry Hive at '{0}'", wimHivePath)); Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", $"Unmounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
wimReg.Unload(); wimReg.Unload();
} }
} }
}
catch (Exception) catch (Exception)
{ {
wimCommitChanges = false; wimCommitChanges = false;
@@ -281,13 +319,13 @@ namespace Disco.ClientBootstrapper.Interop
finally finally
{ {
// Unmount WIM // Unmount WIM
Program.Status.UpdateStatus(null, "Unmounting WIM", string.Format("Unmounting WIM Image at '{0}'", wimMountPath)); Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
Interop.WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, InstallLocation, wimImageIndex, wimCommitChanges); WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, installLocation, wimImageIndex, wimCommitChanges);
if (m_MessageCallback != null) if (m_MessageCallback != null)
{ {
Interop.WIMInterop.WindowsImageContainer.NativeMethods.UnregisterMessageCallback(m_MessageCallback); WIMInterop.WindowsImageContainer.NativeMethods.UnregisterMessageCallback(m_MessageCallback);
m_MessageCallback = null; m_MessageCallback = null;
} }
@@ -295,23 +333,25 @@ namespace Disco.ClientBootstrapper.Interop
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
if (Directory.Exists(wimTempMountPath)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
cancellationToken.ThrowIfCancellationRequested();
} }
} }
else else
{ {
// Online File System // Online File System
Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", string.Format("Install Location: {0}", InstallLocation), true, -1); Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {installLocation}", true, -1);
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
string rootFileSystemLocation = Path.GetPathRoot(InstallLocation); string rootFileSystemLocation = Path.GetPathRoot(installLocation);
RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true); RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true);
string fileSystemInstallLocation = InstallLocation.Substring(rootFileSystemLocation.Length); string fileSystemInstallLocation = installLocation.Substring(rootFileSystemLocation.Length);
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation); await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation, forcedServerUrl, cancellationToken);
Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1); Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1);
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
} }
Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper"); Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper");
Program.SleepThread(1500, false); await Program.SleepThread(1500, false, cancellationToken);
} }
private static uint WimImageEventMessagePump( private static uint WimImageEventMessagePump(
@@ -321,16 +361,16 @@ namespace Disco.ClientBootstrapper.Interop
IntPtr UserData IntPtr UserData
) )
{ {
uint status = (uint)Interop.WIMInterop.WindowsImageContainer.NativeMethods.WIMMessage.WIM_MSG_SUCCESS; uint status = (uint)WIMInterop.WindowsImageContainer.NativeMethods.WIMMessage.WIM_MSG_SUCCESS;
Interop.WIMInterop.DefaultImageEventArgs eventArgs = new Interop.WIMInterop.DefaultImageEventArgs(wParam, lParam, UserData); WIMInterop.DefaultImageEventArgs eventArgs = new WIMInterop.DefaultImageEventArgs(wParam, lParam, UserData);
//System.Diagnostics.Debug.WriteLine(MessageId); //System.Diagnostics.Debug.WriteLine(MessageId);
switch ((Interop.WIMInterop.WindowsImageContainer.ImageEventMessage)MessageId) switch ((WIMInterop.WindowsImageContainer.ImageEventMessage)MessageId)
{ {
case Interop.WIMInterop.WindowsImageContainer.ImageEventMessage.Progress: case WIMInterop.WindowsImageContainer.ImageEventMessage.Progress:
case Interop.WIMInterop.WindowsImageContainer.ImageEventMessage.MountCleanupProgress: case WIMInterop.WindowsImageContainer.ImageEventMessage.MountCleanupProgress:
var timeRemainingMil = eventArgs.LeftParameter.ToInt32(); var timeRemainingMil = eventArgs.LeftParameter.ToInt32();
string timeRemainingMessage; string timeRemainingMessage;
if (timeRemainingMil > 0) if (timeRemainingMil > 0)
@@ -339,7 +379,7 @@ namespace Disco.ClientBootstrapper.Interop
timeRemainingMessage = "Calculating, please wait..."; timeRemainingMessage = "Calculating, please wait...";
var progress = eventArgs.WideParameter.ToInt32(); var progress = eventArgs.WideParameter.ToInt32();
Program.Status.UpdateStatus(null, null, string.Format("Time remaining: {0}", timeRemainingMessage), true, progress); Program.Status.UpdateStatus(null, null, $"Time remaining: {timeRemainingMessage}", true, progress);
break; break;
default: default:
@@ -349,41 +389,28 @@ namespace Disco.ClientBootstrapper.Interop
return status; return status;
} }
public static void Uninstall() public static async Task Uninstall(CancellationToken cancellationToken)
{ {
// Application Directory // Application Directory
var appDirectory = Program.InlinePath.Value; var appDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (Program.AllowUninstall && !appDirectory.StartsWith("\\\\")) if (Program.AllowUninstall && !appDirectory.StartsWith(@"\\"))
{ {
Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0); Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0);
Program.SleepThread(1000, true); await Program.SleepThread(1000, true, cancellationToken);
//var uninstallScriptLocation = System.IO.Path.Combine(appDirectory, "UninstallBootstrapper.vbs");
//if (System.IO.File.Exists(uninstallScriptLocation))
//{
// var bootstrapperPID = System.Diagnostics.Process.GetCurrentProcess().Id;
// var cscriptPath = System.IO.Path.Combine(Environment.SystemDirectory, "cscript.exe");
// var cscriptArgs = string.Format("\"{0}\" /WaitForProcessID:{1}", uninstallScriptLocation, bootstrapperPID);
// var startProc = new ProcessStartInfo(cscriptPath, cscriptArgs);
// startProc.WorkingDirectory = Environment.SystemDirectory;
// startProc.WindowStyle = ProcessWindowStyle.Hidden;
// Process.Start(startProc);
//}
// Remove Registry Entries // Remove Registry Entries
using (var regWinlogon = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) using (var regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.DeleteValue("HideStartupScripts", false); regWinlogon.DeleteValue("HideStartupScripts", false);
regWinlogon.DeleteValue("RunStartupScriptSync", false); regWinlogon.DeleteValue("RunStartupScriptSync", false);
} }
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup", false); Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup", false);
// Restore Registry Backups // Restore Registry Backups
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -391,7 +418,7 @@ namespace Disco.ClientBootstrapper.Interop
RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts");
} }
} }
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{ {
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -401,10 +428,10 @@ namespace Disco.ClientBootstrapper.Interop
} }
// Delete Group Policy Script File // Delete Group Policy Script File
var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\scripts.ini"); var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\scripts.ini");
if (File.Exists(groupPolicyScriptsPath)) if (File.Exists(groupPolicyScriptsPath))
File.Delete(groupPolicyScriptsPath); File.Delete(groupPolicyScriptsPath);
var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\disco_scripts.ini");
if (File.Exists(groupPolicyScriptsBackupPath)) if (File.Exists(groupPolicyScriptsBackupPath))
File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath); File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath);
@@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management; using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -17,8 +14,8 @@ namespace Disco.ClientBootstrapper.Interop
public string Name { get; set; } public string Name { get; set; }
public string NetConnectionID { get; set; } public string NetConnectionID { get; set; }
public string MACAddress { get; set; } public string MACAddress { get; set; }
public UInt64 Speed { get; set; } public ulong Speed { get; set; }
public UInt16 LastConnectionStatus { get; set; } public ushort LastConnectionStatus { get; set; }
public bool IsWireless { get; set; } public bool IsWireless { get; set; }
public string WirelessInterfaceDescription { get; set; } public string WirelessInterfaceDescription { get; set; }
public int LastWirelessConnectionStatus { get; set; } public int LastWirelessConnectionStatus { get; set; }
@@ -30,51 +27,51 @@ namespace Disco.ClientBootstrapper.Interop
private void UpdateFromWmi(ManagementObject wmiObject) private void UpdateFromWmi(ManagementObject wmiObject)
{ {
this.WmiPath = (string)wmiObject.GetPropertyValue("__PATH"); WmiPath = (string)wmiObject.GetPropertyValue("__PATH");
this.Index = (UInt32)wmiObject.GetPropertyValue("Index"); Index = (uint)wmiObject.GetPropertyValue("Index");
this.Guid = Guid.Parse((string)wmiObject.GetPropertyValue("GUID")); Guid = Guid.Parse((string)wmiObject.GetPropertyValue("GUID"));
this.MACAddress = (string)wmiObject.GetPropertyValue("MACAddress"); MACAddress = (string)wmiObject.GetPropertyValue("MACAddress");
this.Name = (string)wmiObject.GetPropertyValue("Name"); Name = (string)wmiObject.GetPropertyValue("Name");
this.NetConnectionID = (string)wmiObject.GetPropertyValue("NetConnectionID"); NetConnectionID = (string)wmiObject.GetPropertyValue("NetConnectionID");
this.Speed = (UInt64)wmiObject.GetPropertyValue("Speed"); Speed = (ulong)wmiObject.GetPropertyValue("Speed");
var connectionStatus = ConnectionStatus; _ = ConnectionStatus;
this.IsWireless = true; IsWireless = true;
try try
{ {
var wirelessConnectionStatus = WirelessConnectionStatus; var wirelessConnectionStatus = WirelessConnectionStatus;
} }
catch (Exception) { catch (Exception)
this.IsWireless = false; {
}; IsWireless = false;
}
} }
public int WirelessConnectionStatus public int WirelessConnectionStatus
{ {
get { get
if (this.IsWireless) {
if (IsWireless)
{ {
IntPtr handle = IntPtr.Zero; IntPtr handle = IntPtr.Zero;
uint negotiatedVersion;
try try
{ {
if (NetworkInterop.WlanOpenHandle(1, IntPtr.Zero, out negotiatedVersion, ref handle) != 0) if (NetworkInterop.WlanOpenHandle(1, IntPtr.Zero, out var negotiatedVersion, ref handle) != 0)
throw new NotSupportedException("This network adapter does not support Wireless"); throw new NotSupportedException("This network adapter does not support Wireless");
IntPtr ptr = new IntPtr(); IntPtr ptr = new IntPtr();
uint dataSize;
var interfaceGuid = this.Guid; var interfaceGuid = Guid;
if (NetworkInterop.WlanQueryInterface(handle, ref interfaceGuid, NetworkInterop.WLAN_INTF_OPCODE.wlan_intf_opcode_interface_state, IntPtr.Zero, out dataSize, ref ptr, IntPtr.Zero) != 0) if (NetworkInterop.WlanQueryInterface(handle, ref interfaceGuid, NetworkInterop.WLAN_INTF_OPCODE.wlan_intf_opcode_interface_state, IntPtr.Zero, out var dataSize, ref ptr, IntPtr.Zero) != 0)
throw new NotSupportedException("This network adapter does not support Wireless"); throw new NotSupportedException("This network adapter does not support Wireless");
this.LastWirelessConnectionStatus = Marshal.ReadInt32(ptr); LastWirelessConnectionStatus = Marshal.ReadInt32(ptr);
NetworkInterop.WlanFreeMemory(ptr); NetworkInterop.WlanFreeMemory(ptr);
return this.LastWirelessConnectionStatus; return LastWirelessConnectionStatus;
} }
finally finally
{ {
@@ -114,46 +111,46 @@ namespace Disco.ClientBootstrapper.Interop
} }
} }
public UInt16 ConnectionStatus public ushort ConnectionStatus
{ {
get get
{ {
using (var wmiObject = new ManagementObject(this.WmiPath)) using (var wmiObject = new ManagementObject(WmiPath))
{ {
this.LastConnectionStatus = (UInt16)wmiObject.GetPropertyValue("NetConnectionStatus"); LastConnectionStatus = (ushort)wmiObject.GetPropertyValue("NetConnectionStatus");
} }
return this.LastConnectionStatus; return LastConnectionStatus;
} }
} }
public string ConnectionStatusMeaning(UInt16 status) public string ConnectionStatusMeaning(ushort status)
{ {
switch (status) switch (status)
{ {
case (UInt16)0: case (ushort)0:
return "Disconnected"; return "Disconnected";
case (UInt16)1: case (ushort)1:
return "Connecting"; return "Connecting";
case (UInt16)2: case (ushort)2:
return "Connected"; return "Connected";
case (UInt16)3: case (ushort)3:
return "Disconnecting"; return "Disconnecting";
case (UInt16)4: case (ushort)4:
return "Hardware not present"; return "Hardware not present";
case (UInt16)5: case (ushort)5:
return "Hardware disabled"; return "Hardware disabled";
case (UInt16)6: case (ushort)6:
return "Hardware malfunction"; return "Hardware malfunction";
case (UInt16)7: case (ushort)7:
return "Media disconnected"; return "Media disconnected";
case (UInt16)8: case (ushort)8:
return "Authenticating"; return "Authenticating";
case (UInt16)9: case (ushort)9:
return "Authentication succeeded"; return "Authentication succeeded";
case (UInt16)10: case (ushort)10:
return "Authentication failed"; return "Authentication failed";
case (UInt16)11: case (ushort)11:
return "Invalid address"; return "Invalid address";
case (UInt16)12: case (ushort)12:
return "Credentials required"; return "Credentials required";
default: default:
return "Unknown"; return "Unknown";
@@ -1,15 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Net.NetworkInformation;
using System.Management; using System.Management;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml; using System.Xml;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
static class NetworkInterop internal static class NetworkInterop
{ {
#region PInvoke #region PInvoke
@@ -165,30 +167,35 @@ namespace Disco.ClientBootstrapper.Interop
} }
} }
public static bool PingDisco(string ServerName) public static bool HasNetworkConnectivity()
{ {
using (Ping p = new Ping()) var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.ToList();
foreach (var nic in nics)
{ {
try if (nic.Supports(NetworkInterfaceComponent.IPv4))
{ {
PingReply pr = p.Send(ServerName, 2000); var ipProps = nic.GetIPProperties();
if (pr.Status == IPStatus.Success) var ipv4Props = ipProps.GetIPv4Properties();
return true; if (ipv4Props.IsAutomaticPrivateAddressingActive)
else continue;
return false;
} return ipProps.UnicastAddresses
catch (Exception) .Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{ .Any();
return false;
}
} }
} }
public static void ConfigureWireless() return false;
}
public static async Task ConfigureWireless(CancellationToken cancellationToken)
{ {
// Add Certificates // Add Certificates
Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates");
CertificateInterop.AddTempCerts(); await CertificateInterop.AddTempCerts(cancellationToken);
// Add Wireless Profiles // Add Wireless Profiles
Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles");
@@ -197,11 +204,10 @@ namespace Disco.ClientBootstrapper.Interop
{ {
IntPtr wlanHandle = IntPtr.Zero; IntPtr wlanHandle = IntPtr.Zero;
uint negotiatedVersion;
try try
{ {
if (WlanOpenHandle(1, IntPtr.Zero, out negotiatedVersion, ref wlanHandle) != 0) if (WlanOpenHandle(1, IntPtr.Zero, out var negotiatedVersion, ref wlanHandle) != 0)
throw new NotSupportedException("This device does not support Wireless"); throw new NotSupportedException("This device does not support Wireless");
// Add Profile to Each Wireless Adapter // Add Profile to Each Wireless Adapter
@@ -210,15 +216,16 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var inlineWirelessProfile in wirelessInlineProfiles) foreach (var inlineWirelessProfile in wirelessInlineProfiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid)) if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid))
{ {
Program.Status.UpdateStatus(null, null, string.Format("Added Wireless Profile: {0}", inlineWirelessProfile.ProfileName)); Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
else else
{ {
Program.Status.UpdateStatus(null, null, string.Format("Unable to add Wireless Profile: {0}", inlineWirelessProfile.ProfileName)); Program.Status.UpdateStatus(null, null, $"Unable to add Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(5000, false); await Program.SleepThread(5000, false, cancellationToken);
} }
} }
} }
@@ -226,7 +233,7 @@ namespace Disco.ClientBootstrapper.Interop
finally finally
{ {
if (wlanHandle != IntPtr.Zero) if (wlanHandle != IntPtr.Zero)
NetworkInterop.WlanCloseHandle(wlanHandle, IntPtr.Zero); WlanCloseHandle(wlanHandle, IntPtr.Zero);
} }
} }
@@ -240,23 +247,23 @@ namespace Disco.ClientBootstrapper.Interop
public bool AddProfile(IntPtr WlanHandle, Guid interfaceGuid) public bool AddProfile(IntPtr WlanHandle, Guid interfaceGuid)
{ {
var pInterfaceGuid = interfaceGuid; var pInterfaceGuid = interfaceGuid;
var pProfileXml = this.ProfileXml; var pProfileXml = ProfileXml;
uint pFlag = 0; uint pFlag = 0;
uint failReason; return WlanSetProfile(WlanHandle, ref pInterfaceGuid, pFlag, pProfileXml, null, true, IntPtr.Zero, out _) == 0;
return (WlanSetProfile(WlanHandle, ref pInterfaceGuid, pFlag, pProfileXml, null, true, IntPtr.Zero, out failReason) == 0);
} }
} }
private static List<WirelessProfile> GetInlineWirelessProfiles() private static List<WirelessProfile> GetInlineWirelessProfiles()
{ {
var inlineProfileFiles = System.IO.Directory.EnumerateFiles(Program.InlinePath.Value, "WLAN_Profile_*.xml").ToList(); var directoryPath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var inlineProfileFiles = Directory.EnumerateFiles(directoryPath, "WLAN_Profile_*.xml").ToList();
var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count); var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count);
foreach (var filename in inlineProfileFiles) foreach (var filename in inlineProfileFiles)
{ {
var profile = new WirelessProfile() var profile = new WirelessProfile()
{ {
Filename = filename, Filename = filename,
ProfileXml = System.IO.File.ReadAllText(filename) ProfileXml = File.ReadAllText(filename)
}; };
var profileXml = new XmlDocument(); var profileXml = new XmlDocument();
profileXml.LoadXml(profile.ProfileXml); profileXml.LoadXml(profile.ProfileXml);
@@ -280,7 +287,7 @@ namespace Disco.ClientBootstrapper.Interop
WlanGetProfileList(WlanHandle, ref pInterfaceGuid, new IntPtr(), ref ppProfileList); WlanGetProfileList(WlanHandle, ref pInterfaceGuid, new IntPtr(), ref ppProfileList);
WLAN_PROFILE_INFO_LIST wlanProfileInfoList = new WLAN_PROFILE_INFO_LIST(ppProfileList); WLAN_PROFILE_INFO_LIST wlanProfileInfoList = new WLAN_PROFILE_INFO_LIST(ppProfileList);
NetworkInterop.WlanFreeMemory(ppProfileList); WlanFreeMemory(ppProfileList);
return wlanProfileInfoList; return wlanProfileInfoList;
} }
@@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
@@ -33,7 +30,7 @@ namespace Disco.ClientBootstrapper.Interop
private static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid); private static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)] [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern int AdjustTokenPrivileges(int tokenhandle, int disableprivs, [MarshalAs(UnmanagedType.Struct)]ref TOKEN_PRIVILEGES Newstate, int bufferlength, int PreivousState, int Returnlength); private static extern int AdjustTokenPrivileges(int tokenhandle, int disableprivs, [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGES Newstate, int bufferlength, int PreivousState, int Returnlength);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int RegLoadKey(uint hKey, string lpSubKey, string lpFile); private static extern int RegLoadKey(uint hKey, string lpSubKey, string lpFile);
@@ -61,16 +58,15 @@ namespace Disco.ClientBootstrapper.Interop
public RegistryInterop(RegistryHives hive, string subKey, string filePath) public RegistryInterop(RegistryHives hive, string subKey, string filePath)
{ {
int token = 0; int token = 0;
int retval = 0;
TOKEN_PRIVILEGES TP = new TOKEN_PRIVILEGES(); TOKEN_PRIVILEGES TP = new TOKEN_PRIVILEGES();
TOKEN_PRIVILEGES TP2 = new TOKEN_PRIVILEGES(); TOKEN_PRIVILEGES TP2 = new TOKEN_PRIVILEGES();
LUID RestoreLuid = new LUID(); LUID RestoreLuid = new LUID();
LUID BackupLuid = new LUID(); LUID BackupLuid = new LUID();
retval = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token); OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token);
retval = LookupPrivilegeValue(null, SE_RESTORE_NAME, ref RestoreLuid); LookupPrivilegeValue(null, SE_RESTORE_NAME, ref RestoreLuid);
retval = LookupPrivilegeValue(null, SE_BACKUP_NAME, ref BackupLuid); LookupPrivilegeValue(null, SE_BACKUP_NAME, ref BackupLuid);
TP.PrivilegeCount = 1; TP.PrivilegeCount = 1;
TP.Attributes = SE_PRIVILEGE_ENABLED; TP.Attributes = SE_PRIVILEGE_ENABLED;
TP.Luid = RestoreLuid; TP.Luid = RestoreLuid;
@@ -78,25 +74,25 @@ namespace Disco.ClientBootstrapper.Interop
TP2.Attributes = SE_PRIVILEGE_ENABLED; TP2.Attributes = SE_PRIVILEGE_ENABLED;
TP2.Luid = BackupLuid; TP2.Luid = BackupLuid;
retval = AdjustTokenPrivileges(token, 0, ref TP, 1024, 0, 0); AdjustTokenPrivileges(token, 0, ref TP, 1024, 0, 0);
retval = AdjustTokenPrivileges(token, 0, ref TP2, 1024, 0, 0); AdjustTokenPrivileges(token, 0, ref TP2, 1024, 0, 0);
uint regHive = (uint)hive; uint regHive = (uint)hive;
this.Hive = hive; Hive = hive;
this.SubKey = subKey; SubKey = subKey;
RegLoadKey(regHive, subKey, filePath); RegLoadKey(regHive, subKey, filePath);
this.IsUnloaded = false; IsUnloaded = false;
} }
public void Unload() public void Unload()
{ {
if (!IsUnloaded) if (!IsUnloaded)
{ {
uint regHive = (uint)this.Hive; uint regHive = (uint)Hive;
string subKey = this.SubKey; string subKey = SubKey;
RegUnLoadKey(regHive, subKey); RegUnLoadKey(regHive, subKey);
this.IsUnloaded = true; IsUnloaded = true;
} }
} }
@@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
@@ -43,21 +39,20 @@ namespace Disco.ClientBootstrapper.Interop
// End Removed 2012-11-23 G# // End Removed 2012-11-23 G#
// Added 2012-11-23 G# - Migrate to Win32 PInvoke Shutdown // Added 2012-11-23 G# - Migrate to Win32 PInvoke Shutdown
bool result;
TokPriv1Luid tp; TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess(); IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero; IntPtr htok = IntPtr.Zero;
result = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1; tp.Count = 1;
tp.Luid = 0; tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_ENABLED; tp.Attr = SE_PRIVILEGE_ENABLED;
result = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid); LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid);
result = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
result = ExitWindowsEx(flag, 0); ExitWindowsEx(flag, 0);
// End Added 2012-11-23 G# // End Added 2012-11-23 G#
} }
+43 -64
View File
@@ -1,10 +1,8 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Globalization;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Xml;
namespace Disco.ClientBootstrapper.Interop.WIMInterop namespace Disco.ClientBootstrapper.Interop.WIMInterop
{ {
@@ -140,8 +138,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
CreateFileAccessPrivate fileAccess = GetMappedFileAccess(access); CreateFileAccessPrivate fileAccess = GetMappedFileAccess(access);
if (fileAccess == CreateFileAccessPrivate.Read && (!File.Exists(imageFilePath) || (CreateFileMode.OpenExisting != mode))) if (fileAccess == CreateFileAccessPrivate.Read && (!File.Exists(imageFilePath) || (CreateFileMode.OpenExisting != mode)))
{ {
throw new System.UnauthorizedAccessException(string.Format(CultureInfo.CurrentCulture, throw new UnauthorizedAccessException("Read access can be specified only with OpenExisting mode or OpenAlways mode when the .wim file does not exist.");
"Read access can be specified only with OpenExisting mode or OpenAlways mode when the .wim file does not exist."));
} }
// //
@@ -152,9 +149,9 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
m_ImageContainerHandle = NativeMethods.CreateFile(imageFilePath, (uint)fileAccess, (uint)mode); m_ImageContainerHandle = NativeMethods.CreateFile(imageFilePath, (uint)fileAccess, (uint)mode);
m_WindowsImageFilePath = imageFilePath; m_WindowsImageFilePath = imageFilePath;
} }
catch (System.DllNotFoundException ex) catch (DllNotFoundException ex)
{ {
throw new System.DllNotFoundException(string.Format(CultureInfo.CurrentCulture, throw new DllNotFoundException(string.Format(
"Unable to load WIM libraries. Make sure the correct DLLs are present (Wimgapi.dll and Xmlrw.dll)."), ex.InnerException); "Unable to load WIM libraries. Make sure the correct DLLs are present (Wimgapi.dll and Xmlrw.dll)."), ex.InnerException);
} }
@@ -164,7 +161,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
//Set the temporary path so that we can write to an image. This //Set the temporary path so that we can write to an image. This
//cannot be %TEMP% as it does not exist on Windows PE //cannot be %TEMP% as it does not exist on Windows PE
// //
string tempDirectory = System.Environment.GetEnvironmentVariable("systemdrive"); string tempDirectory = Environment.GetEnvironmentVariable("systemdrive");
NativeMethods.SetTemporaryPath(m_ImageContainerHandle, tempDirectory); NativeMethods.SetTemporaryPath(m_ImageContainerHandle, tempDirectory);
} }
@@ -173,8 +170,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to open the .wim file {imageFilePath}.");
"Unable to open the .wim file {0}.", imageFilePath));
} }
// //
@@ -481,7 +477,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
// //
//Never commit changes when destroying this object. //Never commit changes when destroying this object.
// //
this.DismountImage(false); DismountImage(false);
} }
GC.KeepAlive(this); GC.KeepAlive(this);
} }
@@ -576,12 +572,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
//1. Leading 0xFEFF //1. Leading 0xFEFF
//2. Be in <IMAGE></IMAGE> //2. Be in <IMAGE></IMAGE>
// //
string formattedXml = String.Format(CultureInfo.InvariantCulture, string formattedXml = $"{UNICODE_FILE_MARKER}{"<IMAGE>"}{imageInformation}{"</IMAGE>"}";
"{0}{1}{2}{3}",
UNICODE_FILE_MARKER,
"<IMAGE>",
imageInformation,
"</IMAGE>");
NativeMethods.SetImageInformation(m_ImageHandle, formattedXml); NativeMethods.SetImageInformation(m_ImageHandle, formattedXml);
GC.KeepAlive(this); GC.KeepAlive(this);
@@ -597,7 +588,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
//Mount the image //Mount the image
// //
m_MountedPath = pathToMountTo; m_MountedPath = pathToMountTo;
NativeMethods.MountImage(pathToMountTo, m_ParentWindowsImageFilePath, m_Index, System.IO.Path.GetTempPath()); NativeMethods.MountImage(pathToMountTo, m_ParentWindowsImageFilePath, m_Index, Path.GetTempPath());
m_Mounted = true; m_Mounted = true;
} }
@@ -650,7 +641,8 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
///Private null constructor ///Private null constructor
///</summary> ///</summary>
private private
NativeMethods() { } NativeMethods()
{ }
[DllImport("Wimgapi.dll", ExactSpelling = true, [DllImport("Wimgapi.dll", ExactSpelling = true,
EntryPoint = "WIMCreateFile", EntryPoint = "WIMCreateFile",
@@ -684,16 +676,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
IntPtr windowsImageHandle = IntPtr.Zero; IntPtr windowsImageHandle = IntPtr.Zero;
int rc = -1; int rc = -1;
windowsImageHandle = NativeMethods.WimCreateFile(imageFile, access, mode, 0, 0, out creationResult); windowsImageHandle = WimCreateFile(imageFile, access, mode, 0, 0, out _);
rc = Marshal.GetLastWin32Error(); rc = Marshal.GetLastWin32Error();
if (windowsImageHandle == IntPtr.Zero) if (windowsImageHandle == IntPtr.Zero)
{ {
// //
//Everything failed; throw an exception //Everything failed; throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to open/create .wim file {imageFile}. Error = {rc}");
"Unable to open/create .wim file {0}. Error = {1}",
imageFile, rc));
} }
return windowsImageHandle; return windowsImageHandle;
@@ -721,15 +711,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
void void
CloseHandle(IntPtr handle) CloseHandle(IntPtr handle)
{ {
bool status = NativeMethods.WimCloseHandle(handle); bool status = WimCloseHandle(handle);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (status == false) if (status == false)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to close image handle. Error = {rc}");
"Unable to close image handle. Error = {0}", rc));
} }
} }
@@ -757,14 +746,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
void void
SetTemporaryPath(IntPtr handle, string temporaryPath) SetTemporaryPath(IntPtr handle, string temporaryPath)
{ {
bool status = NativeMethods.WimSetTemporaryPath(handle, temporaryPath); bool status = WimSetTemporaryPath(handle, temporaryPath);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (status == false) if (status == false)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to set temporary path. Error = {0}", rc)); throw new InvalidOperationException($"Unable to set temporary path. Error = {rc}");
} }
} }
@@ -794,14 +783,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
{ {
//Load the image data based on the .wim handle //Load the image data based on the .wim handle
// //
IntPtr hWim = NativeMethods.WimLoadImage(handle, (uint)imageIndex); IntPtr hWim = WimLoadImage(handle, (uint)imageIndex);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (hWim == IntPtr.Zero) if (hWim == IntPtr.Zero)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to load image. Error = {0}", rc)); throw new InvalidOperationException($"Unable to load image. Error = {rc}");
} }
return hWim; return hWim;
@@ -832,15 +821,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
IntPtr IntPtr
CaptureImage(IntPtr handle, string path) CaptureImage(IntPtr handle, string path)
{ {
IntPtr hImage = NativeMethods.WimCaptureImage(handle, path, 0); IntPtr hImage = WimCaptureImage(handle, path, 0);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (hImage == IntPtr.Zero) if (hImage == IntPtr.Zero)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Failed to capture image from {path}. Error = {rc}");
"Failed to capture image from {0}. Error = {1}", path, rc));
} }
return hImage; return hImage;
} }
@@ -873,14 +861,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
int int
GetImageCount(IntPtr windowsImageHandle) GetImageCount(IntPtr windowsImageHandle)
{ {
int count = NativeMethods.WimGetImageCount(windowsImageHandle); int count = WimGetImageCount(windowsImageHandle);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (count == -1) if (count == -1)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to get image count. Error = {0}", rc)); throw new InvalidOperationException($"Unable to get image count. Error = {rc}");
} }
return count; return count;
@@ -971,28 +959,25 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
try try
{ {
status = NativeMethods.WimMountImage(mountPath, status = WimMountImage(mountPath,
windowsImageFileName, windowsImageFileName,
(uint)imageIndex, (uint)imageIndex,
temporaryPath); temporaryPath);
rc = Marshal.GetLastWin32Error(); rc = Marshal.GetLastWin32Error();
} }
catch (System.StackOverflowException) catch (StackOverflowException)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to mount image {windowsImageFileName} to {mountPath}.");
"Unable to mount image {0} to {1}.", windowsImageFileName, mountPath));
} }
if (status == false) if (status == false)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to mount image {windowsImageFileName} to {mountPath}. Error = {rc}");
"Unable to mount image {0} to {1}. Error = {2}",
windowsImageFileName, mountPath, rc));
} }
} }
@@ -1022,15 +1007,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
// //
//Call WimApplyImage always with the Index flag for performance reasons. //Call WimApplyImage always with the Index flag for performance reasons.
// //
bool status = NativeMethods.WimApplyImage(imageHandle, applicationPath, NativeMethods.WIM_FLAG_INDEX); bool status = WimApplyImage(imageHandle, applicationPath, WIM_FLAG_INDEX);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (status == false) if (status == false)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to apply image to {applicationPath}. Error = {rc}");
"Unable to apply image to {0}. Error = {1}", applicationPath, rc));
} }
} }
@@ -1062,7 +1046,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
IntPtr info = IntPtr.Zero, sizeOfInfo = IntPtr.Zero; IntPtr info = IntPtr.Zero, sizeOfInfo = IntPtr.Zero;
bool status; bool status;
status = NativeMethods.WimGetImageInformation(handle, out info, out sizeOfInfo); status = WimGetImageInformation(handle, out info, out _);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (status == false) if (status == false)
@@ -1070,8 +1054,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to get image information. Error = {rc}");
"Unable to get image information. Error = {0}", rc));
} }
string s = Marshal.PtrToStringUni(info); string s = Marshal.PtrToStringUni(info);
@@ -1112,15 +1095,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
IntPtr xmlBuffer = Marshal.AllocHGlobal(byteBufferSize); IntPtr xmlBuffer = Marshal.AllocHGlobal(byteBufferSize);
Marshal.Copy(byteBuffer, 0, xmlBuffer, byteBufferSize); Marshal.Copy(byteBuffer, 0, xmlBuffer, byteBufferSize);
bool status = NativeMethods.WimSetImageInformation(handle, xmlBuffer, (uint)byteBufferSize); bool status = WimSetImageInformation(handle, xmlBuffer, (uint)byteBufferSize);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (status == false) if (status == false)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to set image information. Error = {rc}");
"Unable to set image information. Error = {0}", rc));
} }
} }
@@ -1153,16 +1135,15 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
try try
{ {
status = NativeMethods.WimUnmountImage(mountPath, wimdowsImageFileName, (uint)imageIndex, commitChanges); status = WimUnmountImage(mountPath, wimdowsImageFileName, (uint)imageIndex, commitChanges);
rc = Marshal.GetLastWin32Error(); rc = Marshal.GetLastWin32Error();
} }
catch (System.StackOverflowException ex) catch (StackOverflowException ex)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.StackOverflowException(string.Format(CultureInfo.CurrentCulture, throw new StackOverflowException($"Unable to unmount image {wimdowsImageFileName} from {mountPath}.",
"Unable to unmount image {0} from {1}.", wimdowsImageFileName, mountPath),
ex.InnerException); ex.InnerException);
} }
if (status == false) if (status == false)
@@ -1170,9 +1151,7 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, throw new InvalidOperationException($"Unable to unmount image {wimdowsImageFileName} from {mountPath}. Error = {rc}");
"Unable to unmount image {0} from {1}. Error = {2}",
wimdowsImageFileName, mountPath, rc));
} }
} }
@@ -1221,14 +1200,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
void void
RegisterCallback(MessageCallback callback) RegisterCallback(MessageCallback callback)
{ {
uint callbackZeroBasedIndex = NativeMethods.WimRegisterMessageCallback(IntPtr.Zero, callback, IntPtr.Zero); WimRegisterMessageCallback(IntPtr.Zero, callback, IntPtr.Zero);
int rc = Marshal.GetLastWin32Error(); int rc = Marshal.GetLastWin32Error();
if (rc != 0) if (rc != 0)
{ {
// //
//Throw an exception //Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to register message callback.")); throw new InvalidOperationException("Unable to register message callback.");
} }
} }
@@ -1253,14 +1232,14 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
void void
UnregisterMessageCallback(MessageCallback registeredCallback) UnregisterMessageCallback(MessageCallback registeredCallback)
{ {
bool status = NativeMethods.WimUnregisterMessageCallback(IntPtr.Zero, registeredCallback); bool status = WimUnregisterMessageCallback(IntPtr.Zero, registeredCallback);
int rc = Marshal.GetLastWin32Error(); _ = Marshal.GetLastWin32Error();
if (status != true) if (status != true)
{ {
// //
// Throw an exception // Throw an exception
// //
throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to unregister message callback.")); throw new InvalidOperationException("Unable to unregister message callback.");
} }
} }
@@ -1358,13 +1337,13 @@ namespace Disco.ClientBootstrapper.Interop.WIMInterop
fileAccess = CreateFileAccessPrivate.Write; fileAccess = CreateFileAccessPrivate.Write;
break; break;
default: default:
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "No file access level specified.")); throw new ArgumentException("No file access level specified.");
} }
return fileAccess; return fileAccess;
} }
//[CLSCompliant(false)] //[CLSCompliant(false)]
[FlagsAttribute] [Flags]
private private
enum enum
CreateFileAccessPrivate : uint CreateFileAccessPrivate : uint
+1 -6
View File
@@ -1,9 +1,4 @@
using System; namespace Disco.ClientBootstrapper
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Disco.ClientBootstrapper
{ {
class NullStatus : IStatus class NullStatus : IStatus
{ {
+59 -56
View File
@@ -1,36 +1,58 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
static class Program internal static class Program
{ {
public static IStatus Status { get; set; } private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public static BootstrapperLoop BootstrapperLoop { get; set; } public static IStatus Status { get; private set; }
public static InstallLoop InstallLoop { get; set; }
public static List<string> PostBootstrapperActions { get; set; } public static List<string> PostBootstrapperActions { get; set; }
public static bool AllowUninstall { get; set; } public static bool AllowUninstall { get; private set; }
public static bool ApplicationExiting { get; set; } public static Uri ForcedServerUrl { get; private set; } = null;
public static Lazy<string> InlinePath = new Lazy<string>(() =>
{
return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
});
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main(string[] args) private static void Main(string[] args)
{ {
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
if (args.Length > 0) if (args.Length > 0)
{ {
#if DEBUG
if (args.Any(a => a.Equals("debug", StringComparison.OrdinalIgnoreCase)))
{
do
{
Console.WriteLine("Waiting for Debugger to Attach");
Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached);
}
#endif
if (args.Any(a => a.StartsWith("http://", StringComparison.OrdinalIgnoreCase)))
throw new ArgumentException("Only HTTPS URLs are supported for a forced server URL.");
var forcedServerArg = args.FirstOrDefault(a => a.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
if (forcedServerArg != null)
{
if (Uri.TryCreate(forcedServerArg, UriKind.Absolute, out var forcedUri))
ForcedServerUrl = forcedUri;
else
throw new ArgumentException("The provided forced server URL is not valid.");
}
switch (args[0].ToLower()) switch (args[0].ToLower())
{ {
case "/install": case "/install":
@@ -46,14 +68,17 @@ namespace Disco.ClientBootstrapper
wimImage = args[2]; wimImage = args[2];
if (args.Length > 3) if (args.Length > 3)
tempPath = args[3]; tempPath = args[3];
InstallLoop = new InstallLoop(installLocation, wimImage, tempPath); var installLoop = new InstallLoop(installLocation, wimImage, tempPath, InstallComplete, ForcedServerUrl);
InstallLoop.Start(new InstallLoop.CompleteCallback(InstallComplete)); installLoop.Start();
Application.Run(); Application.Run();
return; return;
case "/uninstall": case "/uninstall":
AllowUninstall = true; AllowUninstall = true;
Status = new NullStatus(); Status = new NullStatus();
Interop.InstallInterop.Uninstall(); Task.Run(async () =>
{
await Interop.InstallInterop.Uninstall(cancellationTokenSource.Token);
}).Wait(cancellationTokenSource.Token);
return; return;
case "/allowuninstall": case "/allowuninstall":
AllowUninstall = true; AllowUninstall = true;
@@ -71,13 +96,13 @@ namespace Disco.ClientBootstrapper
statusForm.Show(); statusForm.Show();
} }
BootstrapperLoop = new BootstrapperLoop(Status, new BootstrapperLoop.LoopCompleteCallback(LoopComplete)); var bootstrapperLoop = new BootstrapperLoop(Status, ForcedServerUrl, LoopComplete, cancellationTokenSource.Token);
BootstrapperLoop.Start(); bootstrapperLoop.Start();
Application.Run(); Application.Run();
} }
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{ {
WriteAppError(e.Exception); WriteAppError(e.Exception);
} }
@@ -86,21 +111,21 @@ namespace Disco.ClientBootstrapper
{ {
try try
{ {
string AppErrorPath = string.Format("{0}{1}", System.Reflection.Assembly.GetExecutingAssembly().Location, ".errors.txt"); string AppErrorPath = $"{System.Reflection.Assembly.GetExecutingAssembly().Location}.errors.txt";
System.Text.StringBuilder ErrorMessage = new System.Text.StringBuilder(); System.Text.StringBuilder ErrorMessage = new System.Text.StringBuilder();
ErrorMessage.AppendLine(); ErrorMessage.AppendLine();
ErrorMessage.AppendLine(DateTime.Now.ToLongDateString()); ErrorMessage.AppendLine(DateTime.Now.ToLongDateString());
ErrorMessage.AppendLine(DateTime.Now.ToLongTimeString()); ErrorMessage.AppendLine(DateTime.Now.ToLongTimeString());
ErrorMessage.AppendLine(string.Format("Type: {0}", ex.GetType().FullName)); ErrorMessage.AppendLine($"Type: {ex.GetType().FullName}");
ErrorMessage.AppendLine(string.Format("Message: {0}", ex.Message)); ErrorMessage.AppendLine($"Message: {ex.Message}");
ErrorMessage.AppendLine(string.Format("Source: {0}", ex.Source)); ErrorMessage.AppendLine($"Source: {ex.Source}");
ErrorMessage.AppendLine(string.Format("Stack: {0}", ex.StackTrace)); ErrorMessage.AppendLine($"Stack: {ex.StackTrace}");
System.IO.File.AppendAllText(AppErrorPath, ErrorMessage.ToString()); System.IO.File.AppendAllText(AppErrorPath, ErrorMessage.ToString());
} }
catch (Exception) { } catch (Exception) { }
} }
public static void LoopComplete() public static async Task LoopComplete(CancellationToken cancellationToken)
{ {
// Run Post Actions // Run Post Actions
if (PostBootstrapperActions != null) if (PostBootstrapperActions != null)
@@ -108,33 +133,32 @@ namespace Disco.ClientBootstrapper
// Check Uninstall // Check Uninstall
if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper")) if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper"))
{ {
Interop.InstallInterop.Uninstall(); await Interop.InstallInterop.Uninstall(cancellationToken);
} }
// Check ShutdownActions // Check ShutdownActions
if (PostBootstrapperActions.Contains("Shutdown")) if (PostBootstrapperActions.Contains("Shutdown"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Shutdown(); Interop.ShutdownInterop.Shutdown();
} }
else else if (PostBootstrapperActions.Contains("Reboot"))
if (PostBootstrapperActions.Contains("Reboot"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Reboot(); Interop.ShutdownInterop.Reboot();
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
ExitApplication(); ExitApplication();
@@ -147,33 +171,12 @@ namespace Disco.ClientBootstrapper
public static void ExitApplication() public static void ExitApplication()
{ {
if (!ApplicationExiting) if (!cancellationTokenSource.IsCancellationRequested)
{ cancellationTokenSource.Cancel();
ApplicationExiting = true;
if (BootstrapperLoop != null)
{
if (BootstrapperLoop.LoopThread != null)
{
if (BootstrapperLoop.LoopThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
{
BootstrapperLoop.LoopThread.Interrupt();
}
if (BootstrapperLoop.LoopThread.ThreadState == System.Threading.ThreadState.Running)
{
BootstrapperLoop.LoopThread.Abort();
}
}
}
Application.Exit(); Application.Exit();
} }
}
public static void Trace(string Format, params string[] args) public static async Task SleepThread(int millisecondsTimeout, bool updateUI, CancellationToken cancellationToken)
{
System.Diagnostics.Debug.WriteLine(Format, args);
}
public static void SleepThread(int millisecondsTimeout, bool updateUI)
{ {
if (updateUI) if (updateUI)
{ {
@@ -181,12 +184,12 @@ namespace Disco.ClientBootstrapper
{ {
int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100)); int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100));
Status.UpdateStatus(null, null, null, true, progress); Status.UpdateStatus(null, null, null, true, progress);
Thread.Sleep(500); await Task.Delay(500, cancellationToken);
} }
} }
else else
{ {
Thread.Sleep(millisecondsTimeout); await Task.Delay(millisecondsTimeout, cancellationToken);
} }
} }
} }
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.2.16320.1300")] [assembly: AssemblyVersion("2.5.25262.0000")]
[assembly: AssemblyFileVersion("2.2.16320.1300")] [assembly: AssemblyFileVersion("2.5.25262.0000")]
+1 -1
View File
@@ -15,6 +15,6 @@
</defaultConnectionFactory> </defaultConnectionFactory>
</entityFramework> </entityFramework>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup> </startup>
</configuration> </configuration>
+13 -13
View File
@@ -28,17 +28,17 @@ namespace Disco.Data.Configuration
{ {
get get
{ {
return ConfigurationCache.GetScopeKeys(Database, this.Scope); return ConfigurationCache.GetScopeKeys(Database, Scope);
} }
} }
private void SetValue<T>(string Key, T Value) private void SetValue<T>(string Key, T Value)
{ {
ConfigurationCache.SetValue(Database, this.Scope, Key, Value); ConfigurationCache.Helpers<T>.SetValue(Database, Scope, Key, Value);
} }
private T GetValue<T>(string Key, T Default) private T GetValue<T>(string Key, T Default)
{ {
return ConfigurationCache.GetValue(Database, this.Scope, Key, Default); return ConfigurationCache.Helpers<T>.GetValue(Database, Scope, Key, Default);
} }
protected void Set<T>(T Value, [CallerMemberName] string Key = null) protected void Set<T>(T Value, [CallerMemberName] string Key = null)
@@ -46,7 +46,7 @@ namespace Disco.Data.Configuration
if (Key == null) if (Key == null)
throw new ArgumentNullException("Key"); throw new ArgumentNullException("Key");
this.SetValue(Key, Value); SetValue(Key, Value);
} }
protected T Get<T>(T Default, [CallerMemberName] string Key = null) protected T Get<T>(T Default, [CallerMemberName] string Key = null)
@@ -54,17 +54,17 @@ namespace Disco.Data.Configuration
if (Key == null) if (Key == null)
throw new ArgumentNullException("Key"); throw new ArgumentNullException("Key");
return this.GetValue(Key, Default); return GetValue(Key, Default);
} }
protected void SetObsfucated(string Value, [CallerMemberName] string Key = null) protected void SetObsfucated(string Value, [CallerMemberName] string Key = null)
{ {
this.Set(Value.Obsfucate(), Key); Set(Value.Obsfucate(), Key);
} }
protected string GetDeobsfucated(string Default, [CallerMemberName] string Key = null) protected string GetDeobsfucated(string Default, [CallerMemberName] string Key = null)
{ {
var obsfucatedValue = this.Get<string>(null, Key); var obsfucatedValue = Get<string>(null, Key);
if (obsfucatedValue == null) if (obsfucatedValue == null)
return Default; return Default;
@@ -74,35 +74,35 @@ namespace Disco.Data.Configuration
protected void RemoveScope() protected void RemoveScope()
{ {
ConfigurationCache.RemoveScope(Database, this.Scope); ConfigurationCache.RemoveScope(Database, Scope);
} }
protected void RemoveItem(string Key) protected void RemoveItem(string Key)
{ {
ConfigurationCache.RemoveScopeKey(Database, this.Scope, Key); ConfigurationCache.RemoveScopeKey(Database, Scope, Key);
} }
[Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")] [Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")]
protected void SetAsJson<T>(T Value, [CallerMemberName] string Key = null) protected void SetAsJson<T>(T Value, [CallerMemberName] string Key = null)
{ {
this.Set(Value, Key); Set(Value, Key);
} }
[Obsolete("Types are automatically serialized/deserialized if required, use Get<T>(T Default) instead.")] [Obsolete("Types are automatically serialized/deserialized if required, use Get<T>(T Default) instead.")]
protected T GetFromJson<T>(T Default, [CallerMemberName] string Key = null) protected T GetFromJson<T>(T Default, [CallerMemberName] string Key = null)
{ {
return this.Get(Default, Key); return Get(Default, Key);
} }
[Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")] [Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")]
protected void SetAsEnum<T>(T Value, [CallerMemberName] string Key = null) protected void SetAsEnum<T>(T Value, [CallerMemberName] string Key = null)
{ {
this.Set(Value, Key); Set(Value, Key);
} }
[Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")] [Obsolete("Types are automatically serialized/deserialized if required, use Set<T>(T Value) instead.")]
protected T GetFromEnum<T>(T Default, [CallerMemberName] string Key = null) protected T GetFromEnum<T>(T Default, [CallerMemberName] string Key = null)
{ {
return this.Get(Default, Key); return Get(Default, Key);
} }
} }
} }
+79 -41
View File
@@ -22,11 +22,11 @@ namespace Disco.Data.Configuration
private static ConfigurationCacheType Cache(DiscoDataContext Database) private static ConfigurationCacheType Cache(DiscoDataContext Database)
{ {
if (ConfigurationCache.cacheStore == null) if (cacheStore == null)
{ {
lock (configChangeLock) lock (configChangeLock)
{ {
if (ConfigurationCache.cacheStore == null) if (cacheStore == null)
{ {
if (Database == null) if (Database == null)
throw new InvalidOperationException("The Configuration must be loaded at least once before a Cache-Only Configuration Context is used"); throw new InvalidOperationException("The Configuration must be loaded at least once before a Cache-Only Configuration Context is used");
@@ -46,18 +46,16 @@ namespace Disco.Data.Configuration
} }
} }
return ConfigurationCache.cacheStore; return cacheStore;
} }
private static ConfigurationCacheItemType CacheGetItem(DiscoDataContext Database, string Scope, string Key) private static ConfigurationCacheItemType CacheGetItem(DiscoDataContext Database, string Scope, string Key)
{ {
var cache = Cache(Database); var cache = Cache(Database);
ConfigurationCacheScopeType scopeCache; if (cache.TryGetValue(Scope, out var scopeCache))
if (cache.TryGetValue(Scope, out scopeCache))
{ {
ConfigurationCacheItemType item = default(ConfigurationCacheItemType); if (scopeCache.TryGetValue(Key, out var item))
if (scopeCache.TryGetValue(Key, out item))
return item; return item;
} }
@@ -93,8 +91,7 @@ namespace Disco.Data.Configuration
Database.ConfigurationItems.Add(configItem); Database.ConfigurationItems.Add(configItem);
// Add Item to Cache // Add Item to Cache
ConfigurationCacheScopeType scopeCache; if (!cacheStore.TryGetValue(Scope, out var scopeCache))
if (!cacheStore.TryGetValue(Scope, out scopeCache))
{ {
scopeCache = new ConfigurationCacheScopeType(); scopeCache = new ConfigurationCacheScopeType();
cacheStore.TryAdd(Scope, scopeCache); cacheStore.TryAdd(Scope, scopeCache);
@@ -134,10 +131,9 @@ namespace Disco.Data.Configuration
Database.ConfigurationItems.Remove(configItem); Database.ConfigurationItems.Remove(configItem);
// Remove item from Cache // Remove item from Cache
ConfigurationCacheScopeType scopeCache; if (cacheStore.TryGetValue(Scope, out var scopeCache))
if (cacheStore.TryGetValue(Scope, out scopeCache))
{ {
scopeCache.TryRemove(Key, out item); scopeCache.TryRemove(Key, out _);
} }
return null; return null;
@@ -148,8 +144,7 @@ namespace Disco.Data.Configuration
configItem.Value = Value; configItem.Value = Value;
// Update Cache // Update Cache
ConfigurationCacheScopeType scopeCache; if (cacheStore.TryGetValue(Scope, out var scopeCache))
if (cacheStore.TryGetValue(Scope, out scopeCache))
{ {
scopeCache.TryRemove(Key, out item); scopeCache.TryRemove(Key, out item);
item = new ConfigurationCacheItemType(configItem, ObjectValue); item = new ConfigurationCacheItemType(configItem, ObjectValue);
@@ -165,10 +160,9 @@ namespace Disco.Data.Configuration
} }
private static ConfigurationCacheItemType SetItemTypeValue(ConfigurationCacheItemType ExistingItem, object Value) private static ConfigurationCacheItemType SetItemTypeValue(ConfigurationCacheItemType ExistingItem, object Value)
{ {
var cache = ConfigurationCache.cacheStore; var cache = cacheStore;
ConfigurationCacheScopeType scopeCache; if (cache.TryGetValue(ExistingItem.Item1.Scope, out var scopeCache))
if (cache.TryGetValue(ExistingItem.Item1.Scope, out scopeCache))
{ {
ConfigurationCacheItemType newItem = new ConfigurationCacheItemType(ExistingItem.Item1, Value); ConfigurationCacheItemType newItem = new ConfigurationCacheItemType(ExistingItem.Item1, Value);
scopeCache.TryUpdate(ExistingItem.Item1.Key, newItem, ExistingItem); scopeCache.TryUpdate(ExistingItem.Item1.Key, newItem, ExistingItem);
@@ -183,18 +177,18 @@ namespace Disco.Data.Configuration
#region Helpers #region Helpers
private static bool IsConvertableFromString(Type t) private static bool IsConvertableFromString(Type t)
{ {
if (t == typeof(Boolean) || if (t == typeof(bool) ||
t == typeof(Char) || t == typeof(char) ||
t == typeof(SByte) || t == typeof(sbyte) ||
t == typeof(Byte) || t == typeof(byte) ||
t == typeof(Int16) || t == typeof(UInt16) || t == typeof(short) || t == typeof(ushort) ||
t == typeof(Int32) || t == typeof(UInt32) || t == typeof(int) || t == typeof(uint) ||
t == typeof(Int64) || t == typeof(UInt64) || t == typeof(long) || t == typeof(ulong) ||
t == typeof(Single) || t == typeof(float) ||
t == typeof(Double) || t == typeof(double) ||
t == typeof(Decimal) || t == typeof(decimal) ||
t == typeof(DateTime) || t == typeof(DateTime) ||
t == typeof(String)) t == typeof(string))
return true; return true;
else else
return false; return false;
@@ -202,7 +196,11 @@ namespace Disco.Data.Configuration
#endregion #endregion
#region Cache Getters/Setters #region Cache Getters/Setters
internal static T GetValue<T>(DiscoDataContext Database, string Scope, string Key, T Default) internal static class Helpers<T>
{
private static readonly IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
internal static T GetValue(DiscoDataContext Database, string Scope, string Key, T Default)
{ {
var item = CacheGetItem(Database, Scope, Key); var item = CacheGetItem(Database, Scope, Key);
@@ -238,9 +236,34 @@ namespace Disco.Data.Configuration
} }
else if (itemType.BaseType != null && itemType.BaseType == typeof(Enum)) else if (itemType.BaseType != null && itemType.BaseType == typeof(Enum))
{ {
// Enum // enum
itemValue = Enum.Parse(typeof(T), item.Item1.Value); itemValue = Enum.Parse(typeof(T), item.Item1.Value);
} }
else if (itemType.IsGenericType &&
itemType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
IsConvertableFromString(Nullable.GetUnderlyingType(itemType)))
{
// nullable
itemValue = (T)Convert.ChangeType(item.Item1.Value, Nullable.GetUnderlyingType(itemType));
}
else if (itemType == typeof(Guid))
{
// guid
itemValue = new Guid(item.Item1.Value);
}
else if (itemType == typeof(Guid?))
{
// guid
if (string.IsNullOrEmpty(item.Item1.Value))
itemValue = null;
else
itemValue = new Guid(item.Item1.Value);
}
else if (itemType == typeof(byte[]))
{
// byte[]
itemValue = Convert.FromBase64String(item.Item1.Value);
}
else else
{ {
// JSON Deserialize // JSON Deserialize
@@ -254,18 +277,18 @@ namespace Disco.Data.Configuration
} }
} }
} }
internal static void SetValue<T>(DiscoDataContext Database, string Scope, string Key, T Value) internal static void SetValue(DiscoDataContext Database, string Scope, string Key, T Value)
{ {
Type valueType = typeof(T); Type valueType = typeof(T);
string stringValue; string stringValue;
if (Value == null) if (comparer.Equals(Value, default))
{ {
stringValue = null; stringValue = null;
} }
else if (valueType == typeof(object)) else if (valueType == typeof(object))
{ {
throw new ArgumentException(string.Format("Cannot serialize the configuration item [{0}].[{1}] which defines a type of [System.Object]", Scope, Key), "Value"); throw new ArgumentException($"Cannot serialize the configuration item [{Scope}].[{Key}] which has the type [System.Object]", "Value");
} }
else if (IsConvertableFromString(valueType)) else if (IsConvertableFromString(valueType))
{ {
@@ -274,9 +297,25 @@ namespace Disco.Data.Configuration
} }
else if (valueType.BaseType != null && valueType.BaseType == typeof(Enum)) else if (valueType.BaseType != null && valueType.BaseType == typeof(Enum))
{ {
// Enum // enum
stringValue = Value.ToString(); stringValue = Value.ToString();
} }
else if (valueType.IsGenericType &&
valueType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
IsConvertableFromString(Nullable.GetUnderlyingType(valueType)))
{
// nullable
stringValue = Value.ToString();
}
else if (valueType == typeof(Guid) || valueType == typeof(Guid?))
{
stringValue = Value.ToString();
}
else if (Value is byte[] valueBytes)
{
// byte[]
stringValue = Convert.ToBase64String(valueBytes);
}
else else
{ {
// JSON Serialize // JSON Serialize
@@ -285,6 +324,8 @@ namespace Disco.Data.Configuration
CacheSetItem(Database, Scope, Key, stringValue, Value); CacheSetItem(Database, Scope, Key, stringValue, Value);
} }
}
#endregion #endregion
#region Cache Helpers #region Cache Helpers
@@ -293,8 +334,7 @@ namespace Disco.Data.Configuration
{ {
var cache = Cache(Database); var cache = Cache(Database);
ConfigurationCacheScopeType scopeCache; if (cache.TryGetValue(Scope, out var scopeCache))
if (cache.TryGetValue(Scope, out scopeCache))
{ {
return scopeCache.Keys.ToList(); return scopeCache.Keys.ToList();
} }
@@ -318,8 +358,7 @@ namespace Disco.Data.Configuration
// Remove item from Cache // Remove item from Cache
if (cacheStore != null) if (cacheStore != null)
{ {
ConfigurationCacheScopeType scopeCache; cacheStore.TryRemove(Scope, out var scopeCache);
cacheStore.TryRemove(Scope, out scopeCache);
} }
} }
} }
@@ -359,10 +398,9 @@ namespace Disco.Data.Configuration
// Remove item from Cache // Remove item from Cache
if (cacheItem != null) if (cacheItem != null)
{ {
ConfigurationCacheScopeType scopeCache; if (cacheStore.TryGetValue(Scope, out var scopeCache))
if (cacheStore.TryGetValue(Scope, out scopeCache))
{ {
scopeCache.TryRemove(Key, out cacheItem); scopeCache.TryRemove(Key, out _);
} }
} }
} }
@@ -1,5 +1,7 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Disco.Data.Configuration.Modules namespace Disco.Data.Configuration.Modules
{ {
@@ -24,12 +26,37 @@ namespace Disco.Data.Configuration.Modules
} }
} }
[Obsolete("User SearchAllServers instead"), EditorBrowsable(EditorBrowsableState.Never)]
public bool? SearchAllForestServers public bool? SearchAllForestServers
{ {
get { return Get<bool?>(null); } get { return Get<bool?>(null); }
set { Set(value); } set { Set(value); }
} }
public bool? SearchAllServers
{
get
{
var value = Get<bool?>(null);
/// migrate <see cref="SearchAllForestServers"/>
#pragma warning disable CS0618 // Type or member is obsolete
if (value == null)
{
value = SearchAllForestServers;
if (value != null)
{
SearchAllForestServers = null;
SearchAllServers = value;
}
}
#pragma warning restore CS0618 // Type or member is obsolete
return value;
}
set { Set(value); }
}
/// <summary> /// <summary>
/// If true LDAP filters contain wildcards only at the end of the search term. /// If true LDAP filters contain wildcards only at the end of the search term.
/// This greatly improves performance in very large AD environments (ie: EDU001/EDU002) /// This greatly improves performance in very large AD environments (ie: EDU001/EDU002)
@@ -1,4 +1,5 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using System;
namespace Disco.Data.Configuration.Modules namespace Disco.Data.Configuration.Modules
{ {
@@ -6,33 +7,24 @@ namespace Disco.Data.Configuration.Modules
{ {
public BootstrapperConfiguration(DiscoDataContext Database) : base(Database) { } public BootstrapperConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope public override string Scope { get; } = "Bootstrapper";
{
get { return "Bootstrapper"; }
}
public string MacSshUsername public string MacSshUsername
{ {
get get => Get("root");
{ set => Set(value);
return this.Get("root");
}
set
{
this.Set(value);
}
} }
public string MacSshPassword public string MacSshPassword
{ {
get get => GetDeobsfucated(string.Empty);
{ set => SetObsfucated(value);
return this.GetDeobsfucated(string.Empty);
} }
set
public TimeSpan PendingTimeout
{ {
this.SetObsfucated(value); get => TimeSpan.FromSeconds(Get(30 * 60)); // 30 minutes default
} set => Set((int)value.TotalSeconds);
} }
} }
} }
@@ -0,0 +1,18 @@
using Disco.Data.Repository;
using Disco.Models.Services.Devices.DeviceFlag;
namespace Disco.Data.Configuration.Modules
{
public class DeviceFlagsConfiguration : ConfigurationBase
{
public DeviceFlagsConfiguration(DiscoDataContext database) : base(database) { }
public override string Scope { get; } = "DeviceFlags";
public DeviceFlagExportOptions LastExportOptions
{
get => Get(DeviceFlagExportOptions.DefaultOptions());
set => Set(value);
}
}
}
@@ -7,13 +7,13 @@ namespace Disco.Data.Configuration.Modules
{ {
public DeviceProfilesConfiguration(DiscoDataContext Database) : base(Database) { } public DeviceProfilesConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope { get { return "DeviceProfiles"; } } public override string Scope { get; } = "DeviceProfiles";
public int DefaultDeviceProfileId public int DefaultDeviceProfileId
{ {
get get
{ {
var v = this.Get(1); var v = Get(1);
if (v > 0) if (v > 0)
return v; return v;
else else
@@ -24,21 +24,21 @@ namespace Disco.Data.Configuration.Modules
if (value < 1) if (value < 1)
throw new ArgumentOutOfRangeException("value", "Expected >= 1"); throw new ArgumentOutOfRangeException("value", "Expected >= 1");
this.Set(value); Set(value);
} }
} }
public int DefaultAddDeviceOfflineDeviceProfileId public int DefaultAddDeviceOfflineDeviceProfileId
{ {
get get
{ {
return this.Get(0); return Get(0);
} }
set set
{ {
if (value < 0) if (value < 0)
throw new ArgumentOutOfRangeException("value", "Expected >= 0"); throw new ArgumentOutOfRangeException("value", "Expected >= 0");
this.Set(value); Set(value);
} }
} }
@@ -1,5 +1,5 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Services.Devices.Exporting; using Disco.Models.Services.Devices;
namespace Disco.Data.Configuration.Modules namespace Disco.Data.Configuration.Modules
{ {
@@ -7,12 +7,18 @@ namespace Disco.Data.Configuration.Modules
{ {
public DevicesConfiguration(DiscoDataContext Database) : base(Database) { } public DevicesConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope { get { return "Devices"; } } public override string Scope { get; } = "Devices";
public DeviceExportOptions LastExportOptions public DeviceExportOptions LastExportOptions
{ {
get { return this.Get<DeviceExportOptions>(DeviceExportOptions.DefaultOptions()); } get => Get(DeviceExportOptions.DefaultOptions());
set { this.Set(value); } set => Set(value);
}
public bool EnrollmentLegacyDiscoveryDisabled
{
get => Get(false);
set => Set(value);
} }
} }
} }
@@ -8,12 +8,18 @@ namespace Disco.Data.Configuration.Modules
{ {
public DocumentsConfiguration(DiscoDataContext Database) : base(Database) { } public DocumentsConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope { get { return "Documents"; } } public override string Scope { get; } = "Documents";
public List<DocumentTemplatePackage> Packages public List<DocumentTemplatePackage> Packages
{ {
get { return Get<List<DocumentTemplatePackage>>(null); } get { return Get<List<DocumentTemplatePackage>>(null); }
set { Set(value); } set { Set(value); }
} }
public DocumentExportOptions LastExportOptions
{
get => Get(DocumentExportOptions.DefaultOptions());
set => Set(value);
}
} }
} }
@@ -1,5 +1,5 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Services.Job; using Disco.Models.Services.Jobs;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -9,18 +9,27 @@ namespace Disco.Data.Configuration.Modules
{ {
public JobPreferencesConfiguration(DiscoDataContext Database) : base(Database) { } public JobPreferencesConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope { get { return "JobPreferences"; } } public override string Scope { get; } = "JobPreferences";
/// <summary>
/// Initial comments template for new jobs
/// </summary>
public string InitialCommentsTemplate
{
get => Get<string>(null);
set => Set(value);
}
/// <summary> /// <summary>
/// Number of days a job is open before it is considered 'Long Running' /// Number of days a job is open before it is considered 'Long Running'
/// </summary> /// </summary>
public int LongRunningJobDaysThreshold public int LongRunningJobDaysThreshold
{ {
get { return Get<int>(7); } get => Get(7);
set set
{ {
if (value < 0) if (value < 0)
throw new ArgumentOutOfRangeException("value", "The Long Running Job Days Threshold cannot be less than zero"); throw new ArgumentOutOfRangeException(nameof(value), "The Long Running Job Days Threshold cannot be less than zero");
Set(value); Set(value);
} }
@@ -31,27 +40,33 @@ namespace Disco.Data.Configuration.Modules
/// </summary> /// </summary>
public int StaleJobMinutesThreshold public int StaleJobMinutesThreshold
{ {
get { return Get<int>(60 * 24 * 2); } // Default to 48 Hours (2 days) get => Get(60 * 24 * 2); // Default to 48 Hours (2 days)
set set
{ {
if (value < 0) if (value < 0)
throw new ArgumentOutOfRangeException("value", "The Stale Job Minutes Threshold cannot be less than zero"); throw new ArgumentOutOfRangeException(nameof(value), "The Stale Job Minutes Threshold cannot be less than zero");
Set(value); Set(value);
} }
} }
public bool LodgmentIncludeAllAttachmentsByDefault
{
get => Get(false);
set => Set(value);
}
/// <summary> /// <summary>
/// Theme used in noticeboards by default. /// Theme used in noticeboards by default.
/// <see cref="Disco.Services.Extensions.UIHelpers.NoticeboardThemes"/> /// <see cref="Disco.Services.Extensions.UIHelpers.NoticeboardThemes"/>
/// </summary> /// </summary>
public string DefaultNoticeboardTheme public string DefaultNoticeboardTheme
{ {
get { return Get("default"); } get => Get("default");
set set
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("DefaultNoticeboardTheme"); throw new ArgumentNullException(nameof(DefaultNoticeboardTheme));
Set(value); Set(value);
} }
@@ -59,26 +74,48 @@ namespace Disco.Data.Configuration.Modules
public LocationModes LocationMode public LocationModes LocationMode
{ {
get { return Get(LocationModes.Unrestricted); } get => Get(LocationModes.Unrestricted);
set { Set(value); } set => Set(value);
} }
public List<string> LocationList public List<string> LocationList
{ {
get { return Get(new List<string>()); } get => Get(new List<string>());
set { Set(value); } set => Set(value);
} }
public string OnCreateExpression public string OnCreateExpression
{ {
get { return Get<string>(null); } get => Get<string>(null);
set { Set(value); } set => Set(value);
}
public string OnDeviceReadyForReturnExpression
{
get => Get<string>(null);
set => Set(value);
} }
public string OnCloseExpression public string OnCloseExpression
{ {
get { return Get<string>(null); } get => Get<string>(null);
set { Set(value); } set => Set(value);
}
public JobExportOptions LastExportOptions
{
get => Get(JobExportOptions.DefaultOptions());
set
{
Set(value);
LastExportDate = DateTime.Now;
}
}
public DateTime? LastExportDate
{
get => Get<DateTime?>(null);
set => Set(value);
} }
} }
} }
@@ -1,6 +1,5 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.BI.Config; using Disco.Models.BI.Config;
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -8,11 +7,9 @@ namespace Disco.Data.Configuration.Modules
{ {
public class OrganisationAddressesConfiguration : ConfigurationBase public class OrganisationAddressesConfiguration : ConfigurationBase
{ {
private const string scope = "OrganisationAddresses";
public OrganisationAddressesConfiguration(DiscoDataContext Database) : base(Database) { } public OrganisationAddressesConfiguration(DiscoDataContext Database) : base(Database) { }
public override string Scope { get { return scope; } } public override string Scope { get; } = "OrganisationAddresses";
public OrganisationAddress GetAddress(int Id) public OrganisationAddress GetAddress(int Id)
{ {
@@ -58,19 +55,5 @@ namespace Disco.Data.Configuration.Modules
} }
} }
internal static void MigrateDatabase(DiscoDataContext Database)
{
// Migrate all organisation addresses to JSON
if (Database.ConfigurationItems.Count(i => i.Scope == scope && !i.Value.StartsWith("{")) > 0)
{
var items = Database.ConfigurationItems.Where(i => i.Scope == scope && !i.Value.StartsWith("{")).ToList();
items.ForEach(i =>
{
i.Value = JsonConvert.SerializeObject(OrganisationAddress.FromConfigurationEntry(int.Parse(i.Key), i.Value));
});
Database.SaveChanges();
}
}
} }
} }
@@ -0,0 +1,18 @@
using Disco.Data.Repository;
using Disco.Models.Services.Users.UserFlags;
namespace Disco.Data.Configuration.Modules
{
public class UserFlagsConfiguration : ConfigurationBase
{
public UserFlagsConfiguration(DiscoDataContext database) : base(database) { }
public override string Scope { get; } = "UserFlags";
public UserFlagExportOptions LastExportOptions
{
get => Get(UserFlagExportOptions.DefaultOptions());
set => Set(value);
}
}
}
+126 -109
View File
@@ -1,6 +1,8 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Services.Exporting;
using Disco.Models.Services.Interop.DiscoServices; using Disco.Models.Services.Interop.DiscoServices;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
namespace Disco.Data.Configuration namespace Disco.Data.Configuration
@@ -18,6 +20,8 @@ namespace Disco.Data.Configuration
moduleActiveDirectoryConfiguration = new Lazy<Modules.ActiveDirectoryConfiguration>(() => new Modules.ActiveDirectoryConfiguration(Database)); moduleActiveDirectoryConfiguration = new Lazy<Modules.ActiveDirectoryConfiguration>(() => new Modules.ActiveDirectoryConfiguration(Database));
moduleDevicesConfiguration = new Lazy<Modules.DevicesConfiguration>(() => new Modules.DevicesConfiguration(Database)); moduleDevicesConfiguration = new Lazy<Modules.DevicesConfiguration>(() => new Modules.DevicesConfiguration(Database));
moduleDocumentsConfiguration = new Lazy<Modules.DocumentsConfiguration>(() => new Modules.DocumentsConfiguration(Database)); moduleDocumentsConfiguration = new Lazy<Modules.DocumentsConfiguration>(() => new Modules.DocumentsConfiguration(Database));
moduleUserFlagsConfiguration = new Lazy<Modules.UserFlagsConfiguration>(() => new Modules.UserFlagsConfiguration(Database));
moduleDeviceFlagsConfiguration = new Lazy<Modules.DeviceFlagsConfiguration>(() => new Modules.DeviceFlagsConfiguration(Database));
} }
#region Configuration Modules #region Configuration Modules
@@ -29,61 +33,22 @@ namespace Disco.Data.Configuration
private Lazy<Modules.ActiveDirectoryConfiguration> moduleActiveDirectoryConfiguration; private Lazy<Modules.ActiveDirectoryConfiguration> moduleActiveDirectoryConfiguration;
private Lazy<Modules.DevicesConfiguration> moduleDevicesConfiguration; private Lazy<Modules.DevicesConfiguration> moduleDevicesConfiguration;
private Lazy<Modules.DocumentsConfiguration> moduleDocumentsConfiguration; private Lazy<Modules.DocumentsConfiguration> moduleDocumentsConfiguration;
private Lazy<Modules.UserFlagsConfiguration> moduleUserFlagsConfiguration;
private Lazy<Modules.DeviceFlagsConfiguration> moduleDeviceFlagsConfiguration;
public Modules.BootstrapperConfiguration Bootstrapper public Modules.BootstrapperConfiguration Bootstrapper => moduleBootstrapperConfiguration.Value;
{ public Modules.DeviceProfilesConfiguration DeviceProfiles => moduleDeviceProfilesConfiguration.Value;
get public Modules.OrganisationAddressesConfiguration OrganisationAddresses => moduleOrganisationAddressesConfiguration.Value;
{ public Modules.JobPreferencesConfiguration JobPreferences => moduleJobPreferencesConfiguration.Value;
return moduleBootstrapperConfiguration.Value; public Modules.ActiveDirectoryConfiguration ActiveDirectory => moduleActiveDirectoryConfiguration.Value;
} public Modules.DevicesConfiguration Devices => moduleDevicesConfiguration.Value;
} public Modules.DocumentsConfiguration Documents => moduleDocumentsConfiguration.Value;
public Modules.DeviceProfilesConfiguration DeviceProfiles public Modules.UserFlagsConfiguration UserFlags => moduleUserFlagsConfiguration.Value;
{ public Modules.DeviceFlagsConfiguration DeviceFlags => moduleDeviceFlagsConfiguration.Value;
get
{
return moduleDeviceProfilesConfiguration.Value;
}
}
public Modules.OrganisationAddressesConfiguration OrganisationAddresses
{
get
{
return moduleOrganisationAddressesConfiguration.Value;
}
}
public Modules.JobPreferencesConfiguration JobPreferences
{
get
{
return moduleJobPreferencesConfiguration.Value;
}
}
public Modules.ActiveDirectoryConfiguration ActiveDirectory
{
get
{
return moduleActiveDirectoryConfiguration.Value;
}
}
public Modules.DevicesConfiguration Devices
{
get
{
return moduleDevicesConfiguration.Value;
}
}
public Modules.DocumentsConfiguration Documents
{
get
{
return moduleDocumentsConfiguration.Value;
}
}
#endregion #endregion
public override string Scope { get { return "System"; } } public override string Scope { get; } = "System";
public string DataStoreLocation public string DataStoreLocation
{ {
@@ -93,26 +58,27 @@ namespace Disco.Data.Configuration
if (result == null) if (result == null)
{ {
var appDataPath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); var appDataPath = System.Web.HttpContext.Current.Server.MapPath("~/App_Data");
if (appDataPath.EndsWith("\\"))
if (!appDataPath.EndsWith(@"\"))
appDataPath += @"\";
return appDataPath; return appDataPath;
else
return string.Concat(appDataPath, '\\');
} }
else else
return result; return result;
} }
set set
{ {
if (value == null) if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("value"); throw new ArgumentNullException(nameof(value));
if (!System.IO.Directory.Exists(value))
throw new System.IO.DirectoryNotFoundException(string.Format("DataStoreLocation: '{0}' could not be found", value)); if (!Directory.Exists(value))
string storePath; throw new DirectoryNotFoundException($"DataStoreLocation: '{value}' could not be found");
if (value.EndsWith("\\"))
storePath = value; if (!value.EndsWith(@"\"))
else value += @"\";
storePath = string.Concat(value, '\\');
Set(storePath); Set(value);
} }
} }
@@ -129,26 +95,15 @@ namespace Disco.Data.Configuration
} }
#region Plugin Locations #region Plugin Locations
public string PluginsLocation public string PluginsLocation => Path.Combine(DataStoreLocation, @"Plugins\");
public string PluginStorageLocation => Path.Combine(DataStoreLocation, @"PluginStorage\");
public string PluginPackagesLocation => Path.Combine(DataStoreLocation, @"PluginPackages\");
public string PluginUserPhotosLocation => Path.Combine(DataStoreLocation, @"PluginUserPhotos\");
public DateTime PluginDetailsCacheExpiration
{ {
get get => Get(DateTime.MinValue);
{ set => Set(value);
return System.IO.Path.Combine(DataStoreLocation, @"Plugins\");
}
}
public string PluginStorageLocation
{
get
{
return System.IO.Path.Combine(DataStoreLocation, @"PluginStorage\");
}
}
public string PluginPackagesLocation
{
get
{
return System.IO.Path.Combine(DataStoreLocation, @"PluginPackages\");
}
} }
#endregion #endregion
@@ -158,7 +113,7 @@ namespace Disco.Data.Configuration
{ {
get get
{ {
return System.IO.Path.Combine(DataStoreLocation, "OrganisationLogo.png"); return Path.Combine(DataStoreLocation, "OrganisationLogo.png");
} }
} }
public string OrganisationLogoHash public string OrganisationLogoHash
@@ -180,15 +135,15 @@ namespace Disco.Data.Configuration
if (File.Exists(path)) if (File.Exists(path))
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
else else
return new MemoryStream(Disco.Data.Properties.Resources.EmptyLogo); return new MemoryStream(Properties.Resources.EmptyLogo);
} }
set set
{ {
string organisationLogoPath = OrganisationLogoPath; string organisationLogoPath = OrganisationLogoPath;
if (value == null) if (value == null)
{ {
if (System.IO.File.Exists(organisationLogoPath)) if (File.Exists(organisationLogoPath))
System.IO.File.Delete(organisationLogoPath); File.Delete(organisationLogoPath);
} }
else else
{ {
@@ -271,21 +226,89 @@ namespace Disco.Data.Configuration
} }
#endregion #endregion
#region Email Configuration
public string EmailSmtpServer
{
get => Get<string>(null);
set => Set(value);
}
public int EmailSmtpPort
{
get => Get(25);
set => Set(value);
}
public bool EmailEnableSsl
{
get => Get(false);
set => Set(value);
}
public string EmailFromAddress
{
get => Get<string>(null);
set => Set(value);
}
public string EmailReplyToAddress
{
get => Get<string>(null);
set => Set(value);
}
public string EmailUsername
{
get => Get<string>(null);
set => Set(value);
}
public string EmailPassword
{
get => GetDeobsfucated(null);
set => SetObsfucated(value);
}
#endregion
#region UpdateCheck #region UpdateCheck
public string DeploymentId public bool IsActivated => ActivationId.HasValue;
public DateTime? ActivatedOn
{ {
get get => Get((DateTime?)null);
set => Set(value);
}
public string ActivatedBy
{ {
return Get<string>(null); get => Get((string)null);
set => Set(value);
} }
} public Guid? ActivationId
public string DeploymentSecret
{ {
get get => Get((Guid?)null);
set => Set(value);
}
public byte[] ActivationKey
{ {
return Get<string>(null); get => Get((byte[])null);
set => Set(value);
} }
public bool IsLicensed
{
get => LicenseKey != null && LicenseExpiresOn != null && LicenseExpiresOn > DateTime.UtcNow && LicenseError == null;
} }
public string LicenseKey
{
get => Get<string>(null);
set => Set(value);
}
public DateTime? LicenseExpiresOn
{
get => Get<DateTime?>(null);
set => Set(value);
}
public string LicenseError
{
get => Get<string>(null);
set => Set(value);
}
public string DeploymentId => Get<string>(null);
public string DeploymentSecret => Get<string>(null);
public short DeploymentChecksum public short DeploymentChecksum
{ {
get get
@@ -304,22 +327,10 @@ namespace Disco.Data.Configuration
} }
public UpdateResponseV2 UpdateLastCheckResponse public UpdateResponseV2 UpdateLastCheckResponse
{ {
get get => Get<UpdateResponseV2>(null);
{ set => Set(value);
return Get<UpdateResponseV2>(null);
}
set
{
Set(value);
}
}
public bool UpdateBetaDeployment
{
get
{
return Get(false);
}
} }
public bool UpdateBetaDeployment => Get(false);
public Version InstalledDatabaseVersion public Version InstalledDatabaseVersion
{ {
get get
@@ -337,5 +348,11 @@ namespace Disco.Data.Configuration
} }
#endregion #endregion
public List<SavedExport> SavedExports
{
get => Get(new List<SavedExport>());
set => Set(value);
}
} }
} }
+87 -3
View File
@@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco.Data</RootNamespace> <RootNamespace>Disco.Data</RootNamespace>
<AssemblyName>Disco.Data</AssemblyName> <AssemblyName>Disco.Data</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
@@ -40,8 +40,8 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll</HintPath> <HintPath>..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.ComponentModel.DataAnnotations" />
@@ -77,6 +77,8 @@
<Compile Include="Configuration\ConfigurationCache.cs" /> <Compile Include="Configuration\ConfigurationCache.cs" />
<Compile Include="Configuration\Modules\ActiveDirectoryConfiguration.cs" /> <Compile Include="Configuration\Modules\ActiveDirectoryConfiguration.cs" />
<Compile Include="Configuration\Modules\DevicesConfiguration.cs" /> <Compile Include="Configuration\Modules\DevicesConfiguration.cs" />
<Compile Include="Configuration\Modules\DeviceFlagsConfiguration.cs" />
<Compile Include="Configuration\Modules\UserFlagsConfiguration.cs" />
<Compile Include="Configuration\Modules\DocumentsConfiguration.cs" /> <Compile Include="Configuration\Modules\DocumentsConfiguration.cs" />
<Compile Include="Configuration\Modules\JobPreferencesConfiguration.cs" /> <Compile Include="Configuration\Modules\JobPreferencesConfiguration.cs" />
<Compile Include="Configuration\SystemConfiguration.cs" /> <Compile Include="Configuration\SystemConfiguration.cs" />
@@ -163,6 +165,50 @@
<Compile Include="Migrations\201611100557315_DBv19.Designer.cs"> <Compile Include="Migrations\201611100557315_DBv19.Designer.cs">
<DependentUpon>201611100557315_DBv19.cs</DependentUpon> <DependentUpon>201611100557315_DBv19.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Migrations\202011260525547_DBv20.cs" />
<Compile Include="Migrations\202011260525547_DBv20.Designer.cs">
<DependentUpon>202011260525547_DBv20.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202102110443550_DBv21.cs" />
<Compile Include="Migrations\202102110443550_DBv21.Designer.cs">
<DependentUpon>202102110443550_DBv21.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202304150715559_DBv22.cs" />
<Compile Include="Migrations\202304150715559_DBv22.Designer.cs">
<DependentUpon>202304150715559_DBv22.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202401130531317_DBv23.cs" />
<Compile Include="Migrations\202401130531317_DBv23.Designer.cs">
<DependentUpon>202401130531317_DBv23.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202403030134280_DBv24.cs" />
<Compile Include="Migrations\202403030134280_DBv24.Designer.cs">
<DependentUpon>202403030134280_DBv24.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202412180604170_DBv25.cs" />
<Compile Include="Migrations\202412180604170_DBv25.Designer.cs">
<DependentUpon>202412180604170_DBv25.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202503140520548_DBv26.cs" />
<Compile Include="Migrations\202503140520548_DBv26.Designer.cs">
<DependentUpon>202503140520548_DBv26.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202507110430252_DBv27.cs" />
<Compile Include="Migrations\202507110430252_DBv27.Designer.cs">
<DependentUpon>202507110430252_DBv27.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202507170522576_DBv28.cs" />
<Compile Include="Migrations\202507170522576_DBv28.Designer.cs">
<DependentUpon>202507170522576_DBv28.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202509070209304_DBv29.cs" />
<Compile Include="Migrations\202509070209304_DBv29.Designer.cs">
<DependentUpon>202509070209304_DBv29.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202509180416385_DBv30.cs" />
<Compile Include="Migrations\202509180416385_DBv30.Designer.cs">
<DependentUpon>202509180416385_DBv30.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\Configuration.cs" /> <Compile Include="Migrations\Configuration.cs" />
<Compile Include="Migrations\DiscoDataMigrator.cs" /> <Compile Include="Migrations\DiscoDataMigrator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@@ -235,6 +281,39 @@
<EmbeddedResource Include="Migrations\201611100557315_DBv19.resx"> <EmbeddedResource Include="Migrations\201611100557315_DBv19.resx">
<DependentUpon>201611100557315_DBv19.cs</DependentUpon> <DependentUpon>201611100557315_DBv19.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Migrations\202011260525547_DBv20.resx">
<DependentUpon>202011260525547_DBv20.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202102110443550_DBv21.resx">
<DependentUpon>202102110443550_DBv21.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202304150715559_DBv22.resx">
<DependentUpon>202304150715559_DBv22.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202401130531317_DBv23.resx">
<DependentUpon>202401130531317_DBv23.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202403030134280_DBv24.resx">
<DependentUpon>202403030134280_DBv24.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202412180604170_DBv25.resx">
<DependentUpon>202412180604170_DBv25.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202503140520548_DBv26.resx">
<DependentUpon>202503140520548_DBv26.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202507110430252_DBv27.resx">
<DependentUpon>202507110430252_DBv27.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202507170522576_DBv28.resx">
<DependentUpon>202507170522576_DBv28.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202509070209304_DBv29.resx">
<DependentUpon>202509070209304_DBv29.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202509180416385_DBv30.resx">
<DependentUpon>202509180416385_DBv30.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -246,6 +325,11 @@
<None Include="Resources\EmptyLogo.png" /> <None Include="Resources\EmptyLogo.png" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_StartDate="2000/1/1" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv20 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv20));
string IMigrationMetadata.Id
{
get { return "202011260525547_DBv20"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,39 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv20 : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.DeviceBatchAttachments",
c => new
{
Id = c.Int(nullable: false, identity: true),
DeviceBatchId = c.Int(nullable: false),
TechUserId = c.String(nullable: false, maxLength: 50),
Filename = c.String(nullable: false, maxLength: 500),
MimeType = c.String(nullable: false, maxLength: 500),
Timestamp = c.DateTime(nullable: false),
Comments = c.String(nullable: false, maxLength: 500),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.DeviceBatches", t => t.DeviceBatchId)
.ForeignKey("dbo.Users", t => t.TechUserId)
.Index(t => t.DeviceBatchId)
.Index(t => t.TechUserId);
}
public override void Down()
{
DropIndex("dbo.DeviceBatchAttachments", new[] { "TechUserId" });
DropIndex("dbo.DeviceBatchAttachments", new[] { "DeviceBatchId" });
DropForeignKey("dbo.DeviceBatchAttachments", "TechUserId", "dbo.Users");
DropForeignKey("dbo.DeviceBatchAttachments", "DeviceBatchId", "dbo.DeviceBatches");
DropTable("dbo.DeviceBatchAttachments");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv21 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv21));
string IMigrationMetadata.Id
{
get { return "202102110443550_DBv21"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,34 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv21 : DbMigration
{
public override void Up()
{
AddColumn("dbo.UserAttachments", "HandlerId", c => c.String(maxLength: 30));
AddColumn("dbo.UserAttachments", "HandlerReferenceId", c => c.String(maxLength: 50));
AddColumn("dbo.UserAttachments", "HandlerData", c => c.String());
AddColumn("dbo.JobAttachments", "HandlerId", c => c.String(maxLength: 30));
AddColumn("dbo.JobAttachments", "HandlerReferenceId", c => c.String(maxLength: 50));
AddColumn("dbo.JobAttachments", "HandlerData", c => c.String());
AddColumn("dbo.DeviceAttachments", "HandlerId", c => c.String(maxLength: 30));
AddColumn("dbo.DeviceAttachments", "HandlerReferenceId", c => c.String(maxLength: 50));
AddColumn("dbo.DeviceAttachments", "HandlerData", c => c.String());
}
public override void Down()
{
DropColumn("dbo.DeviceAttachments", "HandlerData");
DropColumn("dbo.DeviceAttachments", "HandlerReferenceId");
DropColumn("dbo.DeviceAttachments", "HandlerId");
DropColumn("dbo.JobAttachments", "HandlerData");
DropColumn("dbo.JobAttachments", "HandlerReferenceId");
DropColumn("dbo.JobAttachments", "HandlerId");
DropColumn("dbo.UserAttachments", "HandlerData");
DropColumn("dbo.UserAttachments", "HandlerReferenceId");
DropColumn("dbo.UserAttachments", "HandlerId");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv22 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv22));
string IMigrationMetadata.Id
{
get { return "202304150715559_DBv22"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,18 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv22 : DbMigration
{
public override void Up()
{
AddColumn("dbo.DocumentTemplates", "OnImportUserFlagRules", c => c.String());
}
public override void Down()
{
DropColumn("dbo.DocumentTemplates", "OnImportUserFlagRules");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv23 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv23));
string IMigrationMetadata.Id
{
get { return "202401130531317_DBv23"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,67 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv23 : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.DeviceFlagAssignments",
c => new
{
Id = c.Int(nullable: false, identity: true),
DeviceFlagId = c.Int(nullable: false),
DeviceSerialNumber = c.String(nullable: false, maxLength: 60),
AddedDate = c.DateTime(nullable: false),
AddedUserId = c.String(nullable: false, maxLength: 50),
RemovedDate = c.DateTime(),
RemovedUserId = c.String(maxLength: 50),
Comments = c.String(),
OnAssignmentExpressionResult = c.String(),
OnUnassignmentExpressionResult = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.DeviceFlags", t => t.DeviceFlagId)
.ForeignKey("dbo.Devices", t => t.DeviceSerialNumber)
.ForeignKey("dbo.Users", t => t.AddedUserId)
.ForeignKey("dbo.Users", t => t.RemovedUserId)
.Index(t => t.DeviceFlagId)
.Index(t => t.DeviceSerialNumber)
.Index(t => t.AddedUserId)
.Index(t => t.RemovedUserId);
CreateTable(
"dbo.DeviceFlags",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(nullable: false, maxLength: 100),
Description = c.String(maxLength: 500),
Icon = c.String(nullable: false, maxLength: 25),
IconColour = c.String(nullable: false, maxLength: 10),
DevicesLinkedGroup = c.String(),
DeviceUsersLinkedGroup = c.String(),
OnAssignmentExpression = c.String(),
OnUnassignmentExpression = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropIndex("dbo.DeviceFlagAssignments", new[] { "RemovedUserId" });
DropIndex("dbo.DeviceFlagAssignments", new[] { "AddedUserId" });
DropIndex("dbo.DeviceFlagAssignments", new[] { "DeviceSerialNumber" });
DropIndex("dbo.DeviceFlagAssignments", new[] { "DeviceFlagId" });
DropForeignKey("dbo.DeviceFlagAssignments", "RemovedUserId", "dbo.Users");
DropForeignKey("dbo.DeviceFlagAssignments", "AddedUserId", "dbo.Users");
DropForeignKey("dbo.DeviceFlagAssignments", "DeviceSerialNumber", "dbo.Devices");
DropForeignKey("dbo.DeviceFlagAssignments", "DeviceFlagId", "dbo.DeviceFlags");
DropTable("dbo.DeviceFlags");
DropTable("dbo.DeviceFlagAssignments");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv24 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv24));
string IMigrationMetadata.Id
{
get { return "202403030134280_DBv24"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,20 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv24 : DbMigration
{
public override void Up()
{
AddColumn("dbo.JobMetaInsurances", "Insurer", c => c.String(maxLength: 200));
AddColumn("dbo.JobMetaInsurances", "InsurerReference", c => c.String(maxLength: 200));
}
public override void Down()
{
DropColumn("dbo.JobMetaInsurances", "InsurerReference");
DropColumn("dbo.JobMetaInsurances", "Insurer");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv25 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv25));
string IMigrationMetadata.Id
{
get { return "202412180604170_DBv25"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,26 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv25 : DbMigration
{
public override void Up()
{
AlterColumn("dbo.UserAttachments", "Comments", c => c.String(maxLength: 500));
AlterColumn("dbo.JobAttachments", "Comments", c => c.String(maxLength: 500));
AlterColumn("dbo.DeviceAttachments", "Comments", c => c.String(maxLength: 500));
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='System' AND [Key] IN ('ActivatedOn', 'ActivationId', 'LicenseExpiresOn')");
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='DocFill' AND [Key] IN ('LicenseExpiresOn')");
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='JobPreferences' AND [Key] IN ('LastExportDate')");
}
public override void Down()
{
AlterColumn("dbo.DeviceAttachments", "Comments", c => c.String(nullable: false, maxLength: 500));
AlterColumn("dbo.JobAttachments", "Comments", c => c.String(nullable: false, maxLength: 500));
AlterColumn("dbo.UserAttachments", "Comments", c => c.String(nullable: false, maxLength: 500));
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv26 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv26));
string IMigrationMetadata.Id
{
get { return "202503140520548_DBv26"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,18 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv26 : DbMigration
{
public override void Up()
{
AlterColumn("dbo.DeviceBatchAttachments", "Comments", c => c.String(maxLength: 500));
}
public override void Down()
{
AlterColumn("dbo.DeviceBatchAttachments", "Comments", c => c.String(nullable: false, maxLength: 500));
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv27 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv27));
string IMigrationMetadata.Id
{
get { return "202507110430252_DBv27"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,58 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv27 : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.UserComments",
c => new
{
Id = c.Int(nullable: false, identity: true),
UserId = c.String(maxLength: 50),
TechUserId = c.String(nullable: false, maxLength: 50),
Timestamp = c.DateTime(nullable: false),
Comments = c.String(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Users", t => t.TechUserId)
.ForeignKey("dbo.Users", t => t.UserId)
.Index(t => t.TechUserId)
.Index(t => t.UserId);
CreateTable(
"dbo.DeviceComments",
c => new
{
Id = c.Int(nullable: false, identity: true),
DeviceSerialNumber = c.String(maxLength: 60),
TechUserId = c.String(nullable: false, maxLength: 50),
Timestamp = c.DateTime(nullable: false),
Comments = c.String(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Users", t => t.TechUserId)
.ForeignKey("dbo.Devices", t => t.DeviceSerialNumber)
.Index(t => t.TechUserId)
.Index(t => t.DeviceSerialNumber);
}
public override void Down()
{
DropIndex("dbo.DeviceComments", new[] { "DeviceSerialNumber" });
DropIndex("dbo.DeviceComments", new[] { "TechUserId" });
DropIndex("dbo.UserComments", new[] { "UserId" });
DropIndex("dbo.UserComments", new[] { "TechUserId" });
DropForeignKey("dbo.DeviceComments", "DeviceSerialNumber", "dbo.Devices");
DropForeignKey("dbo.DeviceComments", "TechUserId", "dbo.Users");
DropForeignKey("dbo.UserComments", "UserId", "dbo.Users");
DropForeignKey("dbo.UserComments", "TechUserId", "dbo.Users");
DropTable("dbo.DeviceComments");
DropTable("dbo.UserComments");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv28 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv28));
string IMigrationMetadata.Id
{
get { return "202507170522576_DBv28"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,40 @@
namespace Disco.Data.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DBv28 : DbMigration
{
public override void Up()
{
AddColumn("dbo.UserFlagAssignments", "RemoveDate", c => c.DateTime());
AddColumn("dbo.UserFlagAssignments", "RemoveUserId", c => c.String(maxLength: 50));
AddColumn("dbo.UserFlags", "Permissions", c => c.String());
AddColumn("dbo.UserFlags", "DefaultRemoveDays", c => c.Int());
AddColumn("dbo.DeviceFlagAssignments", "RemoveDate", c => c.DateTime());
AddColumn("dbo.DeviceFlagAssignments", "RemoveUserId", c => c.String(maxLength: 50));
AddColumn("dbo.DeviceFlags", "Permissions", c => c.String());
AddColumn("dbo.DeviceFlags", "DefaultRemoveDays", c => c.Int());
AddForeignKey("dbo.UserFlagAssignments", "RemoveUserId", "dbo.Users", "Id");
AddForeignKey("dbo.DeviceFlagAssignments", "RemoveUserId", "dbo.Users", "Id");
CreateIndex("dbo.UserFlagAssignments", "RemoveUserId");
CreateIndex("dbo.DeviceFlagAssignments", "RemoveUserId");
}
public override void Down()
{
DropIndex("dbo.DeviceFlagAssignments", new[] { "RemoveUserId" });
DropIndex("dbo.UserFlagAssignments", new[] { "RemoveUserId" });
DropForeignKey("dbo.DeviceFlagAssignments", "RemoveUserId", "dbo.Users");
DropForeignKey("dbo.UserFlagAssignments", "RemoveUserId", "dbo.Users");
DropColumn("dbo.DeviceFlags", "DefaultRemoveDays");
DropColumn("dbo.DeviceFlags", "Permissions");
DropColumn("dbo.DeviceFlagAssignments", "RemoveUserId");
DropColumn("dbo.DeviceFlagAssignments", "RemoveDate");
DropColumn("dbo.UserFlags", "DefaultRemoveDays");
DropColumn("dbo.UserFlags", "Permissions");
DropColumn("dbo.UserFlagAssignments", "RemoveUserId");
DropColumn("dbo.UserFlagAssignments", "RemoveDate");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv29 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv29));
string IMigrationMetadata.Id
{
get { return "202509070209304_DBv29"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,18 @@
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
public partial class DBv29 : DbMigration
{
public override void Up()
{
AddColumn("dbo.DeviceProfiles", "SetAssignedUserForLogon", c => c.Boolean(nullable: false));
Sql("UPDATE dbo.DeviceProfiles SET SetAssignedUserForLogon = 1");
}
public override void Down()
{
DropColumn("dbo.DeviceProfiles", "SetAssignedUserForLogon");
}
}
}
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
// <auto-generated />
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
public sealed partial class DBv30 : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(DBv30));
string IMigrationMetadata.Id
{
get { return "202509180416385_DBv30"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
@@ -0,0 +1,17 @@
namespace Disco.Data.Migrations
{
using System.Data.Entity.Migrations;
public partial class DBv30 : DbMigration
{
public override void Up()
{
AddColumn("dbo.DeviceProfiles", "ProvisionFromOtherDomain", c => c.Boolean(nullable: false, defaultValue: false));
}
public override void Down()
{
DropColumn("dbo.DeviceProfiles", "ProvisionFromOtherDomain");
}
}
}
File diff suppressed because one or more lines are too long
-3
View File
@@ -1,9 +1,6 @@
namespace Disco.Data.Migrations namespace Disco.Data.Migrations
{ {
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations; using System.Data.Entity.Migrations;
using System.Linq;
using Disco.Data.Repository; using Disco.Data.Repository;
internal sealed class Configuration : DbMigrationsConfiguration<DiscoDataContext> internal sealed class Configuration : DbMigrationsConfiguration<DiscoDataContext>
@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Data.Entity.Migrations; using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure; using System.Data.Entity.Migrations.Infrastructure;
using Disco.Data.Repository; using Disco.Data.Repository;
+3 -3
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.2.16320.1300")] [assembly: AssemblyVersion("2.5.25262.0000")]
[assembly: AssemblyFileVersion("2.2.16320.1300")] [assembly: AssemblyFileVersion("2.5.25262.0000")]
+2 -2
View File
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.17929 // Runtime Version:4.0.30319.42000
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@@ -19,7 +19,7 @@ namespace Disco.Data.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {
+10 -7
View File
@@ -1,9 +1,6 @@
using System; using Disco.Models.Repository;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using System.Data.Entity; using System.Data.Entity;
using Disco.Models.Repository;
using System.Data.Entity.ModelConfiguration.Conventions; using System.Data.Entity.ModelConfiguration.Conventions;
namespace Disco.Data.Repository namespace Disco.Data.Repository
@@ -14,7 +11,7 @@ namespace Disco.Data.Repository
public DiscoDataContext() public DiscoDataContext()
{ {
this._Configuration = new Lazy<Configuration.SystemConfiguration>(() => new Configuration.SystemConfiguration(this)); _Configuration = new Lazy<Configuration.SystemConfiguration>(() => new Configuration.SystemConfiguration(this));
} }
public virtual DbSet<ConfigurationItem> ConfigurationItems { get; set; } public virtual DbSet<ConfigurationItem> ConfigurationItems { get; set; }
@@ -22,6 +19,8 @@ namespace Disco.Data.Repository
public virtual DbSet<DocumentTemplate> DocumentTemplates { get; set; } public virtual DbSet<DocumentTemplate> DocumentTemplates { get; set; }
public virtual DbSet<User> Users { get; set; } public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserComment> UserComments { get; set; }
public virtual DbSet<UserDetail> UserDetails { get; set; }
public virtual DbSet<UserAttachment> UserAttachments { get; set; } public virtual DbSet<UserAttachment> UserAttachments { get; set; }
public virtual DbSet<UserFlag> UserFlags { get; set; } public virtual DbSet<UserFlag> UserFlags { get; set; }
public virtual DbSet<UserFlagAssignment> UserFlagAssignments { get; set; } public virtual DbSet<UserFlagAssignment> UserFlagAssignments { get; set; }
@@ -30,12 +29,16 @@ namespace Disco.Data.Repository
public virtual DbSet<DeviceUserAssignment> DeviceUserAssignments { get; set; } public virtual DbSet<DeviceUserAssignment> DeviceUserAssignments { get; set; }
public virtual DbSet<Device> Devices { get; set; } public virtual DbSet<Device> Devices { get; set; }
public virtual DbSet<DeviceComment> DeviceComments { get; set; }
public virtual DbSet<DeviceDetail> DeviceDetails { get; set; } public virtual DbSet<DeviceDetail> DeviceDetails { get; set; }
public virtual DbSet<DeviceModel> DeviceModels { get; set; } public virtual DbSet<DeviceModel> DeviceModels { get; set; }
public virtual DbSet<DeviceProfile> DeviceProfiles { get; set; } public virtual DbSet<DeviceProfile> DeviceProfiles { get; set; }
public virtual DbSet<DeviceBatch> DeviceBatches { get; set; } public virtual DbSet<DeviceBatch> DeviceBatches { get; set; }
public virtual DbSet<DeviceBatchAttachment> DeviceBatchAttachments { get; set; }
public virtual DbSet<DeviceComponent> DeviceComponents { get; set; } public virtual DbSet<DeviceComponent> DeviceComponents { get; set; }
public virtual DbSet<DeviceAttachment> DeviceAttachments { get; set; } public virtual DbSet<DeviceAttachment> DeviceAttachments { get; set; }
public virtual DbSet<DeviceFlag> DeviceFlags { get; set; }
public virtual DbSet<DeviceFlagAssignment> DeviceFlagAssignments { get; set; }
public virtual DbSet<DeviceCertificate> DeviceCertificates { get; set; } public virtual DbSet<DeviceCertificate> DeviceCertificates { get; set; }
@@ -57,7 +60,7 @@ namespace Disco.Data.Repository
{ {
get get
{ {
return this._Configuration.Value; return _Configuration.Value;
} }
} }
+28 -32
View File
@@ -1,11 +1,9 @@
using System; using Disco.Models.Repository;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using Disco.Models.Repository;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.DirectoryServices.ActiveDirectory;
using System.DirectoryServices; using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
namespace Disco.Data.Repository namespace Disco.Data.Repository
{ {
@@ -22,8 +20,6 @@ namespace Disco.Data.Repository
Database.SaveChanges(); Database.SaveChanges();
// Migration Maintenance // Migration Maintenance
Database.MigrateConfiguration();
Database.MigratePreDomainObjects(); Database.MigratePreDomainObjects();
} }
@@ -62,7 +58,7 @@ namespace Disco.Data.Repository
{ {
if (Database.DeviceModels.Count() == 0) if (Database.DeviceModels.Count() == 0)
{ {
Database.DeviceModels.Add(new DeviceModel { Manufacturer = "Unknown", Model = "Unknown", Description = "Unknown Device Model" }); Database.DeviceModels.Add(new DeviceModel { Manufacturer = "Unknown", Model = "Unknown", Description = "Unknown Device Model", ModelType = "Unknown" });
} }
UpdateDeviceModelConfiguration(Database); UpdateDeviceModelConfiguration(Database);
// Removed: 2013-01-14 G# // Removed: 2013-01-14 G#
@@ -212,6 +208,15 @@ namespace Disco.Data.Repository
#endregion #endregion
// End // End
// 2025-07-11
#region "User Management - BYOD" Added
if (Database.JobSubTypes.Count(jst => jst.JobTypeId == JobType.JobTypeIds.UMgmt && jst.Id == JobSubType.UserManagementJobSubTypes.BYOD) == 0)
{
Database.JobSubTypes.Add(new JobSubType { Id = JobSubType.UserManagementJobSubTypes.BYOD, JobTypeId = JobType.JobTypeIds.UMgmt, Description = JobSubType.UserManagementJobSubTypes.BYOD });
}
#endregion
// End
// 2012-05-29 - Audits // 2012-05-29 - Audits
#region "Audit" Added #region "Audit" Added
if (Database.JobSubTypes.Count(jst => jst.JobTypeId == JobType.JobTypeIds.HMisc && jst.Id == "Audit") == 0) if (Database.JobSubTypes.Count(jst => jst.JobTypeId == JobType.JobTypeIds.HMisc && jst.Id == "Audit") == 0)
@@ -259,8 +264,7 @@ namespace Disco.Data.Repository
foreach (var configurationItem in configurationItems) foreach (var configurationItem in configurationItems)
{ {
int profileId = int.Parse(configurationItem.Scope.Substring(configurationItem.Scope.IndexOf(":") + 1)); int profileId = int.Parse(configurationItem.Scope.Substring(configurationItem.Scope.IndexOf(":") + 1));
DeviceProfile dp; if (deviceProfiles.TryGetValue(profileId, out var dp))
if (deviceProfiles.TryGetValue(profileId, out dp))
{ {
switch (configurationItem.Key) switch (configurationItem.Key)
{ {
@@ -268,7 +272,7 @@ namespace Disco.Data.Repository
dp.ComputerNameTemplate = configurationItem.Value; dp.ComputerNameTemplate = configurationItem.Value;
break; break;
case "DistributionType": case "DistributionType":
dp.DistributionType = (DeviceProfile.DistributionTypes)(int.Parse(configurationItem.Value)); dp.DistributionType = (DeviceProfile.DistributionTypes)int.Parse(configurationItem.Value);
break; break;
case "OrganisationalUnit": case "OrganisationalUnit":
dp.OrganisationalUnit = configurationItem.Value; dp.OrganisationalUnit = configurationItem.Value;
@@ -291,7 +295,7 @@ namespace Disco.Data.Repository
private static void UpdateDeviceModelDuplicates(this DiscoDataContext Database) private static void UpdateDeviceModelDuplicates(this DiscoDataContext Database)
{ {
var deviceModels = Database.DeviceModels.ToList(); var deviceModels = Database.DeviceModels.ToList();
var duplicateModels = deviceModels.GroupBy(g => string.Format("{0}|{1}", g.Manufacturer, g.Model)).Where(g => g.Count() > 1); var duplicateModels = deviceModels.GroupBy(g => $"{g.Manufacturer}|{g.Model}").Where(g => g.Count() > 1);
foreach (var duplicateModel in duplicateModels) foreach (var duplicateModel in duplicateModels)
{ {
var primaryModel = duplicateModel.OrderBy(dm => dm.Id).First(); var primaryModel = duplicateModel.OrderBy(dm => dm.Id).First();
@@ -305,23 +309,15 @@ namespace Disco.Data.Repository
if (!redundantModel.Description.EndsWith("** REDUNDANT **")) if (!redundantModel.Description.EndsWith("** REDUNDANT **"))
{ {
if (redundantModel.Description.Length > 484) if (redundantModel.Description.Length > 484)
redundantModel.Description = string.Format("{0} ** REDUNDANT **", redundantModel.Description.Substring(0, 484)); redundantModel.Description = $"{redundantModel.Description.Substring(0, 484)} ** REDUNDANT **";
else else
redundantModel.Description = string.Format("{0} ** REDUNDANT **", redundantModel.Description); redundantModel.Description = $"{redundantModel.Description} ** REDUNDANT **";
} }
} }
} }
} }
// End Added: 2013-02-07 G# // End Added: 2013-02-07 G#
public static void MigrateConfiguration(this DiscoDataContext Database)
{
// Organisation Addresses - Force all to JSON serializing
Configuration.Modules.OrganisationAddressesConfiguration.MigrateDatabase(Database);
Database.SaveChanges();
}
#region Migrate Users SQL #region Migrate Users SQL
private const string MigratePreDomainUsers_Sql = @"INSERT INTO [Users] SELECT @IdNew, u.DisplayName, u.Surname, u.GivenName, u.PhoneNumber, u.EmailAddress FROM [Users] u WHERE [Id]=@IdExisting; private const string MigratePreDomainUsers_Sql = @"INSERT INTO [Users] SELECT @IdNew, u.DisplayName, u.Surname, u.GivenName, u.PhoneNumber, u.EmailAddress FROM [Users] u WHERE [Id]=@IdExisting;
@@ -370,7 +366,7 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
string defaultNamingContext; string defaultNamingContext;
using (Domain d = Domain.GetComputerDomain()) using (Domain d = Domain.GetComputerDomain())
{ {
string ldapPath = string.Format("LDAP://{0}/", d.Name); string ldapPath = $"LDAP://{d.Name}/";
string configurationNamingContext; string configurationNamingContext;
using (var adRootDSE = new DirectoryEntry(ldapPath + "RootDSE")) using (var adRootDSE = new DirectoryEntry(ldapPath + "RootDSE"))
@@ -381,7 +377,7 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
using (var configSearchRoot = new DirectoryEntry(ldapPath + "CN=Partitions," + configurationNamingContext)) using (var configSearchRoot = new DirectoryEntry(ldapPath + "CN=Partitions," + configurationNamingContext))
{ {
var configSearchFilter = string.Format("(&(objectcategory=Crossref)(dnsRoot={0})(netBIOSName=*))", d.Name); var configSearchFilter = $"(&(objectcategory=Crossref)(dnsRoot={d.Name})(netBIOSName=*))";
var configSearchLoadProperites = new string[] { "NetBIOSName" }; var configSearchLoadProperites = new string[] { "NetBIOSName" };
using (var configSearcher = new DirectorySearcher(configSearchRoot, configSearchFilter, configSearchLoadProperites, SearchScope.OneLevel)) using (var configSearcher = new DirectorySearcher(configSearchRoot, configSearchFilter, configSearchLoadProperites, SearchScope.OneLevel))
@@ -403,14 +399,14 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
// Authorization Roles // Authorization Roles
foreach (var authRole in Database.AuthorizationRoles.Where(ar => ar.SubjectIds != null).ToList()) foreach (var authRole in Database.AuthorizationRoles.Where(ar => ar.SubjectIds != null).ToList())
{ {
var ids = string.Join(",", authRole.SubjectIds.Split(',').Select(id => id.Contains('\\') ? id : string.Format("{0}\\{1}", netBiosName, id))); var ids = string.Join(",", authRole.SubjectIds.Split(',').Select(id => id.Contains('\\') ? id : $@"{netBiosName}\{id}"));
if (ids != authRole.SubjectIds) if (ids != authRole.SubjectIds)
authRole.SubjectIds = ids; authRole.SubjectIds = ids;
} }
// Job Queues // Job Queues
foreach (var jobQueue in Database.JobQueues.Where(jq => jq.SubjectIds != null).ToList()) foreach (var jobQueue in Database.JobQueues.Where(jq => jq.SubjectIds != null).ToList())
{ {
var ids = string.Join(",", jobQueue.SubjectIds.Split(',').Select(id => id.Contains('\\') ? id : string.Format("{0}\\{1}", netBiosName, id))); var ids = string.Join(",", jobQueue.SubjectIds.Split(',').Select(id => id.Contains('\\') ? id : $@"{netBiosName}\{id}"));
if (ids != jobQueue.SubjectIds) if (ids != jobQueue.SubjectIds)
jobQueue.SubjectIds = ids; jobQueue.SubjectIds = ids;
} }
@@ -418,9 +414,9 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
foreach (var deviceProfile in Database.DeviceProfiles.Where(dp => dp.OrganisationalUnit == null || !dp.OrganisationalUnit.Contains(@"DC=")).ToList()) foreach (var deviceProfile in Database.DeviceProfiles.Where(dp => dp.OrganisationalUnit == null || !dp.OrganisationalUnit.Contains(@"DC=")).ToList())
{ {
if (string.IsNullOrWhiteSpace(deviceProfile.OrganisationalUnit)) if (string.IsNullOrWhiteSpace(deviceProfile.OrganisationalUnit))
deviceProfile.OrganisationalUnit = string.Format("CN=Computers,{0}", defaultNamingContext); deviceProfile.OrganisationalUnit = $"CN=Computers,{defaultNamingContext}";
else else
deviceProfile.OrganisationalUnit = string.Format("{0},{1}", deviceProfile.OrganisationalUnit, defaultNamingContext); deviceProfile.OrganisationalUnit = $"{deviceProfile.OrganisationalUnit},{defaultNamingContext}";
} }
Database.SaveChanges(); Database.SaveChanges();
@@ -428,7 +424,7 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
var dataStoreLocation = Database.ConfigurationItems.Where(ci => ci.Scope == "System" && ci.Key == "DataStoreLocation").Select(ci => ci.Value).FirstOrDefault(); var dataStoreLocation = Database.ConfigurationItems.Where(ci => ci.Scope == "System" && ci.Key == "DataStoreLocation").Select(ci => ci.Value).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(dataStoreLocation) && System.IO.Directory.Exists(dataStoreLocation)) if (!string.IsNullOrWhiteSpace(dataStoreLocation) && System.IO.Directory.Exists(dataStoreLocation))
{ {
string filePrefix = string.Format("{0}_", netBiosName); string filePrefix = $"{netBiosName}_";
var userAttachmentsDirectory = System.IO.Path.Combine(dataStoreLocation, "UserAttachments"); var userAttachmentsDirectory = System.IO.Path.Combine(dataStoreLocation, "UserAttachments");
if (System.IO.Directory.Exists(userAttachmentsDirectory)) if (System.IO.Directory.Exists(userAttachmentsDirectory))
@@ -451,7 +447,7 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
// MIGRATE DEVICES // MIGRATE DEVICES
foreach (var device in Database.Devices.Where(d => d.DeviceDomainId != null && !d.DeviceDomainId.Contains(@"\")).ToList()) foreach (var device in Database.Devices.Where(d => d.DeviceDomainId != null && !d.DeviceDomainId.Contains(@"\")).ToList())
{ {
device.DeviceDomainId = string.Format("{0}\\{1}", netBiosName, device.DeviceDomainId); device.DeviceDomainId = $@"{netBiosName}\{device.DeviceDomainId}";
} }
Database.SaveChanges(); Database.SaveChanges();
@@ -462,7 +458,7 @@ DELETE [Users] WHERE [Id]=@IdExisting;";
idExisting.Value = user.UserId; idExisting.Value = user.UserId;
SqlParameter idNew = new SqlParameter("@IdNew", System.Data.SqlDbType.NVarChar, 50); SqlParameter idNew = new SqlParameter("@IdNew", System.Data.SqlDbType.NVarChar, 50);
idNew.Value = string.Format("{0}\\{1}", netBiosName, user.UserId); idNew.Value = $@"{netBiosName}\{user.UserId}";
Database.Database.ExecuteSqlCommand(MigratePreDomainUsers_Sql, idExisting, idNew); Database.Database.ExecuteSqlCommand(MigratePreDomainUsers_Sql, idExisting, idNew);
} }
@@ -1,12 +1,8 @@
using System; using Microsoft.Win32;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using System.Data.Entity.Infrastructure;
using Microsoft.Win32;
using System.Data.Common; using System.Data.Common;
using System.Data.Entity.Infrastructure;
using System.Security; using System.Security;
using System.Security.Permissions;
namespace Disco.Data.Repository namespace Disco.Data.Repository
{ {
@@ -52,18 +48,18 @@ namespace Disco.Data.Repository
} }
catch (UnauthorizedAccessException ex) catch (UnauthorizedAccessException ex)
{ {
throw new SecurityException(string.Format("Unable to write to the Registry Location: HKML\\{0}[DatabaseConnectionString]", DiscoRegistryKey), ex); throw new SecurityException($"Unable to write to the Registry Location: HKML\\{DiscoRegistryKey}[DatabaseConnectionString]", ex);
} }
} }
} }
public DiscoDatabaseConnectionFactory(IDbConnectionFactory Default) public DiscoDatabaseConnectionFactory(IDbConnectionFactory Default)
{ {
this.DefaultConnectionFactory = Default; DefaultConnectionFactory = Default;
this.SqlCeConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); SqlCeConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
} }
public System.Data.Common.DbConnection CreateConnection(string nameOrConnectionString) public DbConnection CreateConnection(string nameOrConnectionString)
{ {
if (nameOrConnectionString == "Disco.Data.Repository.DiscoDataContext") if (nameOrConnectionString == "Disco.Data.Repository.DiscoDataContext")
{ {
@@ -71,7 +67,7 @@ namespace Disco.Data.Repository
var connectionString = DiscoDataContextConnectionString; var connectionString = DiscoDataContextConnectionString;
if (connectionString == null) if (connectionString == null)
{ {
throw new InvalidOperationException("The Disco DataContext Connection String has not been configured"); throw new InvalidOperationException("The Disco ICT DataContext Connection String has not been configured");
} }
// Build DiscoDataContext - Use Default Connection Factory (SQLClient) // Build DiscoDataContext - Use Default Connection Factory (SQLClient)
@@ -1,14 +1,11 @@
using System; using Disco.Models.Repository;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reactive;
using System.Reactive.Subjects;
using System.Data.Entity.Infrastructure;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Data.Objects; using System.Data.Objects;
using Disco.Models.Repository; using System.Linq;
using System.Reactive.Subjects;
namespace Disco.Data.Repository.Monitor namespace Disco.Data.Repository.Monitor
{ {
@@ -52,9 +49,8 @@ namespace Disco.Data.Repository.Monitor
private static Type EntityTypeFromProxy(Type EntityProxyType) private static Type EntityTypeFromProxy(Type EntityProxyType)
{ {
Type EntityType;
if (entityProxyTypeCache.TryGetValue(EntityProxyType, out EntityType)) if (entityProxyTypeCache.TryGetValue(EntityProxyType, out var EntityType))
return EntityType; return EntityType;
EntityType = EntityProxyType; EntityType = EntityProxyType;
@@ -113,7 +109,7 @@ namespace Disco.Data.Repository.Monitor
eventType = RepositoryMonitorEventType.Unchanged; eventType = RepositoryMonitorEventType.Unchanged;
break; break;
default: default:
throw new NotSupportedException(string.Format("Database Entry State not supported: {0}", entryState.State.ToString())); throw new NotSupportedException($"Database Entry State not supported: {entryState.State.ToString()}");
} }
entityType = EntityTypeFromProxy(entryState.Entity.GetType()); entityType = EntityTypeFromProxy(entryState.Entity.GetType());
@@ -3,9 +3,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Entity.Infrastructure; using System.Data.Entity.Infrastructure;
using System.Data.Objects; using System.Data.Objects;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Data.Repository.Monitor namespace Disco.Data.Repository.Monitor
{ {
@@ -55,7 +52,8 @@ namespace Disco.Data.Repository.Monitor
return (T)dbEntityState.CurrentValues[PropertyName]; return (T)dbEntityState.CurrentValues[PropertyName];
} }
public void ExecuteAfterCommit(Action<RepositoryMonitorEvent> action){ public void ExecuteAfterCommit(Action<RepositoryMonitorEvent> action)
{
if (afterCommit) if (afterCommit)
{ {
// Execute Immediately // Execute Immediately
@@ -1,10 +1,4 @@
using System; namespace Disco.Data.Repository.Monitor
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Data.Repository.Monitor
{ {
public enum RepositoryMonitorEventType public enum RepositoryMonitorEventType
{ {
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="EntityFramework" version="5.0.0" targetFramework="net45" /> <package id="EntityFramework" version="5.0.0" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.3" targetFramework="net45" /> <package id="Newtonsoft.Json" version="13.0.2" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> <package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5" targetFramework="net45" />
+1 -1
View File
@@ -12,6 +12,6 @@
</defaultConnectionFactory> </defaultConnectionFactory>
</entityFramework> </entityFramework>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup> </startup>
</configuration> </configuration>

Some files were not shown because too many files have changed in this diff Show More