diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c4efe2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,261 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/CodeMaid.config b/CodeMaid.config
new file mode 100644
index 0000000..ae15bb6
--- /dev/null
+++ b/CodeMaid.config
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ <?xml version="1.0" encoding="utf-16"?>
+<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <string>ReSharper disable </string>
+ <string>ReSharper enable </string>
+</ArrayOfString>
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..2e2fca6
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,30 @@
+
+
+ true
+ $(MSBuildThisFileDirectory)\RuleSet.ruleset
+ latest
+
+
+
+
+ 2.1.2
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+ 2.7.1
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+ 1.0.2
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+ 2.2.0.2
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9b8597d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Dionach Limitied
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0013f14
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# What is share auditing?
+
+Share auditing is the process of reviewing file shares within an Active Directory environment in order to determine whether any content is inappropriately accessible.
+
+# What does this tool do?
+
+ShareAudit impersonates the provided credentials and attempts to access the given systems. It will then enumerate all shares, including hidden shares, and retrieve the first level contents of the shares. It will display whether each file or folder is readable or writable to the impersonated user, as well as display the NTFS permissions. For files, ShareAudit will also retrieve the first 1024 bytes of the file, to add in determining whether the file has sensitive content.
+
+Once the initial audit of the first level of all shares is complete, additional folders can also be audited using the right click menu. The reason to only audit the first level of each share automatically is that it is common to see very complex directory structures, that can take a very long time to process, and could be quickly identified as non-sensitive by the auditor.
+
+The right click menu on folders and files also has the option to reveal the item in explorer. This will open a new Explorer window that can be used to browse the share as the impersonated user.
+
+# How do I use this tool?
+
+When creating a new project, you will be first asked for the credentials you wish to impersonate. This would typically be a low privileged user, representative of a normal domain user. In larger organisations, you may wish to run ShareAudit several times in order to represent users from different departments.
+
+When entering credentials, you will need to specify the user's domain (which is not necessarily the same as the domain you wish to test), username, and password.
+
+Alternatively, you can select to use the current Windows credentials, if you are already logged in as the target user. This can be more reliable, as it prevents the need to impersonate the target user.
+
+Next, you will need to enter the scope, which is the list of systems you wish to audit. The scope can either be entered manually or imported from Active Directory.
+
+Once the configuration is complete, you will be presented with the audit page, press start to begin the scan. You will see systems appear and disappear as they are audited and found to be accessible or inaccessible. Whilst you can interact with the result whilst the scan is running, it is best to wait for it to complete to ensure you are working with accurate results.
+
+Once complete, you can browse the result tree and select a share, folder, or file to view more information, including the effective permissions, NTFS permissions, and file previews.
+
+You can also right click folders and files to open in Explorer as the impersonated user and to audit additional folders that may be interesting.
+
+Results can also be exported to a CSV file once the audit is complete for processing with other tools.
+
+# What am I looking for in the results?
+
+Whilst the nature of sensitive data will vary from organisation to organisation, common indicators include:
+
+* Unnecessary writable files or folders. Write access should be as restrictive as possible to limit the impact of ransomware attacks.
+
+* Access to administrative shares (ADMIN$, C$, etc), as this means the impersonated user has local admin rights on the system.
diff --git a/RuleSet.ruleset b/RuleSet.ruleset
new file mode 100644
index 0000000..d3ac2ca
--- /dev/null
+++ b/RuleSet.ruleset
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ShareAudit.sln b/ShareAudit.sln
new file mode 100644
index 0000000..75761db
--- /dev/null
+++ b/ShareAudit.sln
@@ -0,0 +1,63 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.329
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{20E0FD49-F242-4361-809A-111F4F0CE77E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit", "src\Dionach.ShareAudit\Dionach.ShareAudit.csproj", "{1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AC9F4628-6E6A-4BD5-A684-A19CB94249FB}"
+ ProjectSection(SolutionItems) = preProject
+ RuleSet.ruleset = RuleSet.ruleset
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.UserInterface", "src\Dionach.ShareAudit.Modules.UserInterface\Dionach.ShareAudit.Modules.UserInterface.csproj", "{28CF3837-FF58-463B-AF81-E6B0039DE55F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.Services", "src\Dionach.ShareAudit.Modules.Services\Dionach.ShareAudit.Modules.Services.csproj", "{F5BFA34B-3CDE-4C77-9162-96666303FDEA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Model", "src\Dionach.ShareAudit.Model\Dionach.ShareAudit.Model.csproj", "{1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.Services.Interop", "src\Dionach.ShareAudit.Modules.Services.Interop\Dionach.ShareAudit.Modules.Services.Interop.csproj", "{8425D05F-F3F4-4132-9BE1-BED752685333}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8425D05F-F3F4-4132-9BE1-BED752685333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8425D05F-F3F4-4132-9BE1-BED752685333}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8425D05F-F3F4-4132-9BE1-BED752685333}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8425D05F-F3F4-4132-9BE1-BED752685333}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A} = {20E0FD49-F242-4361-809A-111F4F0CE77E}
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F} = {20E0FD49-F242-4361-809A-111F4F0CE77E}
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA} = {20E0FD49-F242-4361-809A-111F4F0CE77E}
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC} = {20E0FD49-F242-4361-809A-111F4F0CE77E}
+ {8425D05F-F3F4-4132-9BE1-BED752685333} = {20E0FD49-F242-4361-809A-111F4F0CE77E}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {04F6815F-15A2-4B74-A2F3-2CF8753BDD87}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs b/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs
new file mode 100644
index 0000000..71953c9
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs
@@ -0,0 +1,79 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Security.AccessControl;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class AccessRuleEntry : INotifyPropertyChanged
+ {
+ private string _identity;
+ private bool _inherited;
+ private string _rights;
+ private AccessControlType _type;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Identity
+ {
+ get => _identity;
+ set
+ {
+ if (value == _identity)
+ {
+ return;
+ }
+
+ _identity = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool Inherited
+ {
+ get => _inherited;
+ set
+ {
+ if (value == _inherited)
+ {
+ return;
+ }
+
+ _inherited = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Rights
+ {
+ get => _rights;
+ set
+ {
+ if (value == _rights)
+ {
+ return;
+ }
+
+ _rights = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public AccessControlType Type
+ {
+ get => _type;
+ set
+ {
+ if (value == _type)
+ {
+ return;
+ }
+
+ _type = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Configuration.cs b/src/Dionach.ShareAudit.Model/Configuration.cs
new file mode 100644
index 0000000..51d6614
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Configuration.cs
@@ -0,0 +1,126 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class Configuration : INotifyPropertyChanged
+ {
+ private Credentials _credentials = new Credentials();
+ private bool _disablePortScan = false;
+ private bool _disableReverseDnsLookup = false;
+ private bool _isReadOnly = false;
+ private string _scope;
+ private bool _useAlternateAuthenticationMethod = true;
+ private bool _useVerbatimScope = false;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public Credentials Credentials
+ {
+ get => _credentials;
+ set
+ {
+ if (ReferenceEquals(value, _credentials))
+ {
+ return;
+ }
+
+ _credentials = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool DisablePortScan
+ {
+ get => _disablePortScan;
+ set
+ {
+ if (value == _disablePortScan)
+ {
+ return;
+ }
+
+ _disablePortScan = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool DisableReverseDnsLookup
+ {
+ get => _disableReverseDnsLookup;
+ set
+ {
+ if (value == _disableReverseDnsLookup)
+ {
+ return;
+ }
+
+ _disableReverseDnsLookup = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get => _isReadOnly;
+ set
+ {
+ if (value == _isReadOnly)
+ {
+ return;
+ }
+
+ _isReadOnly = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Scope
+ {
+ get => _scope;
+ set
+ {
+ if (value == _scope)
+ {
+ return;
+ }
+
+ _scope = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool UseAlternateAuthenticationMethod
+ {
+ get => _useAlternateAuthenticationMethod;
+ set
+ {
+ if (value == _useAlternateAuthenticationMethod)
+ {
+ return;
+ }
+
+ _useAlternateAuthenticationMethod = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool UseVerbatimScope
+ {
+ get => _useVerbatimScope;
+ set
+ {
+ if (value == _useVerbatimScope)
+ {
+ return;
+ }
+
+ _useVerbatimScope = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Credentials.cs b/src/Dionach.ShareAudit.Model/Credentials.cs
new file mode 100644
index 0000000..6dd0323
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Credentials.cs
@@ -0,0 +1,81 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Xml.Serialization;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class Credentials : INotifyPropertyChanged
+ {
+ private string _domain = string.Empty;
+ private string _password = string.Empty;
+ private bool _useCurrentCredentials = false;
+
+ private string _username = string.Empty;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Domain
+ {
+ get => _domain;
+ set
+ {
+ if (value == _domain)
+ {
+ return;
+ }
+
+ _domain = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [XmlIgnore]
+ public string Password
+ {
+ get => _password;
+ set
+ {
+ if (value == _password)
+ {
+ return;
+ }
+
+ _password = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool UseCurrentCredentials
+ {
+ get => _useCurrentCredentials;
+ set
+ {
+ if (value == _useCurrentCredentials)
+ {
+ return;
+ }
+
+ _useCurrentCredentials = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Username
+ {
+ get => _username;
+ set
+ {
+ if (value == _username)
+ {
+ return;
+ }
+
+ _username = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj b/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj
new file mode 100644
index 0000000..c5e9bb0
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj
@@ -0,0 +1,64 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}
+ Library
+ Properties
+ Dionach.ShareAudit.Model
+ Dionach.ShareAudit.Model
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Model/DirectoryEntry.cs b/src/Dionach.ShareAudit.Model/DirectoryEntry.cs
new file mode 100644
index 0000000..9d55ed5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/DirectoryEntry.cs
@@ -0,0 +1,40 @@
+using System.Collections.ObjectModel;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class DirectoryEntry : FileSystemEntry, IFolderEntry
+ {
+ private ObservableCollection _fileSystemEntries = new ObservableCollection();
+ private FolderEntryState _state = FolderEntryState.New;
+
+ public ObservableCollection FileSystemEntries
+ {
+ get => _fileSystemEntries;
+ set
+ {
+ if (ReferenceEquals(value, _fileSystemEntries))
+ {
+ return;
+ }
+
+ _fileSystemEntries = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public FolderEntryState State
+ {
+ get => _state;
+ set
+ {
+ if (value == _state)
+ {
+ return;
+ }
+
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/EffectiveAccess.cs b/src/Dionach.ShareAudit.Model/EffectiveAccess.cs
new file mode 100644
index 0000000..0f2f520
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/EffectiveAccess.cs
@@ -0,0 +1,46 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class EffectiveAccess : INotifyPropertyChanged
+ {
+ private bool _read = false;
+ private bool _write = false;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public bool Read
+ {
+ get => _read;
+ set
+ {
+ if (value == _read)
+ {
+ return;
+ }
+
+ _read = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool Write
+ {
+ get => _write;
+ set
+ {
+ if (value == _write)
+ {
+ return;
+ }
+
+ _write = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/FileEntry.cs b/src/Dionach.ShareAudit.Model/FileEntry.cs
new file mode 100644
index 0000000..0866790
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/FileEntry.cs
@@ -0,0 +1,38 @@
+namespace Dionach.ShareAudit.Model
+{
+ public class FileEntry : FileSystemEntry
+ {
+ private string _head;
+ private FileEntryState _state;
+
+ public string Head
+ {
+ get => _head;
+ set
+ {
+ if (value == _head)
+ {
+ return;
+ }
+
+ _head = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public FileEntryState State
+ {
+ get => _state;
+ set
+ {
+ if (value == _state)
+ {
+ return;
+ }
+
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/FileEntryState.cs b/src/Dionach.ShareAudit.Model/FileEntryState.cs
new file mode 100644
index 0000000..22ac751
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/FileEntryState.cs
@@ -0,0 +1,11 @@
+namespace Dionach.ShareAudit.Model
+{
+ public enum FileEntryState
+ {
+ New,
+ EnumeratingAcls,
+ GettingEffectiveAccess,
+ ReadingHead,
+ Complete
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/FileSystemEntry.cs b/src/Dionach.ShareAudit.Model/FileSystemEntry.cs
new file mode 100644
index 0000000..4a08558
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/FileSystemEntry.cs
@@ -0,0 +1,100 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Xml.Serialization;
+
+namespace Dionach.ShareAudit.Model
+{
+ [XmlInclude(typeof(DirectoryEntry))]
+ [XmlInclude(typeof(FileEntry))]
+ public abstract class FileSystemEntry : INotifyPropertyChanged
+ {
+ private bool _accessible;
+ private ObservableCollection _accessRules;
+ private EffectiveAccess _effectiveAccess = new EffectiveAccess { Read = false, Write = false };
+ private string _fullName;
+
+ private string _name;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public bool Accessible
+ {
+ get => _accessible;
+ set
+ {
+ if (value == _accessible)
+ {
+ return;
+ }
+
+ _accessible = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public virtual ObservableCollection AccessRules
+ {
+ get => _accessRules;
+ set
+ {
+ if (ReferenceEquals(value, _accessRules))
+ {
+ return;
+ }
+
+ _accessRules = value;
+ _accessRules.CollectionChanged += (s, e) => Accessible = _accessRules.Count > 0;
+ OnPropertyChanged();
+ }
+ }
+
+ public EffectiveAccess EffectiveAccess
+ {
+ get => _effectiveAccess;
+ set
+ {
+ if (ReferenceEquals(value, _effectiveAccess))
+ {
+ return;
+ }
+
+ _effectiveAccess = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string FullName
+ {
+ get => _fullName;
+ set
+ {
+ if (value == _fullName)
+ {
+ return;
+ }
+
+ _fullName = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Name
+ {
+ get => _name;
+ set
+ {
+ if (value == _name)
+ {
+ return;
+ }
+
+ _name = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/FolderEntryState.cs b/src/Dionach.ShareAudit.Model/FolderEntryState.cs
new file mode 100644
index 0000000..66c3b22
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/FolderEntryState.cs
@@ -0,0 +1,14 @@
+namespace Dionach.ShareAudit.Model
+{
+ public enum FolderEntryState
+ {
+ New,
+ EnumeratingAcls,
+ GettingEffectiveAccess,
+ EnumerationSuspended,
+ EnumeratingFilesystemEntries,
+ AuditingFileSystemEntries,
+ NestedAuditingSuspended,
+ Complete
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Host.cs b/src/Dionach.ShareAudit.Model/Host.cs
new file mode 100644
index 0000000..84c7c1f
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Host.cs
@@ -0,0 +1,113 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class Host : INotifyPropertyChanged
+ {
+ private bool _accessible;
+ private string _ipAddress;
+ private string _ptrRecord;
+ private ObservableCollection _shares = new ObservableCollection();
+ private HostState _state = HostState.New;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public bool Accessible
+ {
+ get => _accessible;
+ set
+ {
+ if (value == _accessible)
+ {
+ return;
+ }
+
+ _accessible = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string IPAddress
+ {
+ get => _ipAddress;
+ set
+ {
+ if (ReferenceEquals(value, _ipAddress))
+ {
+ return;
+ }
+
+ _ipAddress = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(Name));
+ }
+ }
+
+ public string Name => string.IsNullOrEmpty(PtrRecord) ? IPAddress : PtrRecord;
+
+ public string PtrRecord
+ {
+ get => _ptrRecord;
+ set
+ {
+ if (value == _ptrRecord)
+ {
+ return;
+ }
+
+ _ptrRecord = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(Name));
+ }
+ }
+
+ public ObservableCollection Shares
+ {
+ get => _shares;
+ set
+ {
+ if (ReferenceEquals(value, _shares))
+ {
+ return;
+ }
+
+ _shares = value;
+ _shares.CollectionChanged += (s, e) =>
+ {
+ foreach (Share share in e.NewItems)
+ {
+ share.PropertyChanged += (ss, se) =>
+ {
+ if (se.PropertyName.Equals(nameof(Share.Accessible)))
+ {
+ Accessible = Shares.Any(x => x.Accessible);
+ }
+ };
+ }
+ };
+ OnPropertyChanged();
+ }
+ }
+
+ public HostState State
+ {
+ get => _state;
+ set
+ {
+ if (value == _state)
+ {
+ return;
+ }
+
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/HostState.cs b/src/Dionach.ShareAudit.Model/HostState.cs
new file mode 100644
index 0000000..316158e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/HostState.cs
@@ -0,0 +1,13 @@
+namespace Dionach.ShareAudit.Model
+{
+ public enum HostState
+ {
+ New,
+ LookingUpPtr,
+ CheckingPorts,
+ EnumeratingShares,
+ AuditingShares,
+ NestedAuditingSuspended,
+ Complete
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/IFolderEntry.cs b/src/Dionach.ShareAudit.Model/IFolderEntry.cs
new file mode 100644
index 0000000..fbf2183
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/IFolderEntry.cs
@@ -0,0 +1,17 @@
+using System.Collections.ObjectModel;
+
+namespace Dionach.ShareAudit.Model
+{
+ public interface IFolderEntry
+ {
+ ObservableCollection AccessRules { get; set; }
+
+ EffectiveAccess EffectiveAccess { get; set; }
+
+ ObservableCollection FileSystemEntries { get; set; }
+
+ string FullName { get; set; }
+
+ FolderEntryState State { get; set; }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/ImportComputerType.cs b/src/Dionach.ShareAudit.Model/ImportComputerType.cs
new file mode 100644
index 0000000..bd57f3d
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/ImportComputerType.cs
@@ -0,0 +1,9 @@
+namespace Dionach.ShareAudit.Model
+{
+ public enum ImportComputerType
+ {
+ Servers,
+ Workstations,
+ All,
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Project.cs b/src/Dionach.ShareAudit.Model/Project.cs
new file mode 100644
index 0000000..4184937
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Project.cs
@@ -0,0 +1,63 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class Project : INotifyPropertyChanged
+ {
+ private Configuration _configuration = new Configuration();
+ private ObservableCollection _hosts = new ObservableCollection();
+ private ProjectState _state = ProjectState.New;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public Configuration Configuration
+ {
+ get => _configuration;
+ set
+ {
+ if (ReferenceEquals(value, _configuration))
+ {
+ return;
+ }
+
+ _configuration = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ObservableCollection Hosts
+ {
+ get => _hosts;
+ set
+ {
+ if (ReferenceEquals(value, _hosts))
+ {
+ return;
+ }
+
+ _hosts = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ProjectState State
+ {
+ get => _state;
+ set
+ {
+ if (value == _state)
+ {
+ return;
+ }
+
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/ProjectState.cs b/src/Dionach.ShareAudit.Model/ProjectState.cs
new file mode 100644
index 0000000..195248b
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/ProjectState.cs
@@ -0,0 +1,11 @@
+namespace Dionach.ShareAudit.Model
+{
+ public enum ProjectState
+ {
+ New,
+ Configured,
+ ExpandingScope,
+ AuditingHosts,
+ Complete
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d7a9788
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following set of attributes.
+// Change these attribute values to modify the information associated with an assembly.
+[assembly: AssemblyTitle("Dionach.ShareAudit.Model")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dionach.ShareAudit.Model")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you
+// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1dfc488d-e104-4f35-98da-f23bf6d3f9dc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version Minor Version Build Number Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers by using the '*'
+// as shown below: [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: CLSCompliant(false)]
diff --git a/src/Dionach.ShareAudit.Model/Share.cs b/src/Dionach.ShareAudit.Model/Share.cs
new file mode 100644
index 0000000..d9321c7
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/Share.cs
@@ -0,0 +1,73 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace Dionach.ShareAudit.Model
+{
+ public class Share : FileSystemEntry, INotifyPropertyChanged, IFolderEntry
+ {
+ private ObservableCollection _fileSystemEntries = new ObservableCollection();
+ private string _remark;
+ private FolderEntryState _state = FolderEntryState.New;
+ private ShareTypes _type;
+
+ public ObservableCollection FileSystemEntries
+ {
+ get => _fileSystemEntries;
+ set
+ {
+ if (ReferenceEquals(value, _fileSystemEntries))
+ {
+ return;
+ }
+
+ _fileSystemEntries = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Remark
+ {
+ get => _remark;
+ set
+ {
+ if (value == _remark)
+ {
+ return;
+ }
+
+ _remark = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public FolderEntryState State
+ {
+ get => _state;
+ set
+ {
+ if (value == _state)
+ {
+ return;
+ }
+
+ _state = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ShareTypes Type
+ {
+ get => _type;
+ set
+ {
+ if (value == _type)
+ {
+ return;
+ }
+
+ _type = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Model/ShareType.cs b/src/Dionach.ShareAudit.Model/ShareType.cs
new file mode 100644
index 0000000..6599e0f
--- /dev/null
+++ b/src/Dionach.ShareAudit.Model/ShareType.cs
@@ -0,0 +1,72 @@
+using System;
+
+namespace Dionach.ShareAudit.Model
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
+ [Flags]
+ public enum ShareTypes : uint
+ {
+ Disktree = 0x00000000,
+
+ PrintQueue = 0x00000001,
+
+ Device = 0x00000002,
+
+ Ipc = 0x00000003,
+
+ ClusterFS = 0x02000000,
+
+ ClusterSofs = 0x04000000,
+
+ ClusterDfs = 0x08000000,
+
+ Temporary = 0x40000000,
+
+ Special = 0x80000000,
+
+ SpecialDisktree = Special | Disktree,
+
+ SpecialPrintQueue = Special | PrintQueue,
+
+ SpecialDevice = Special | Device,
+
+ SpecialIpc = Special | Ipc,
+
+ SpecialClusterFS = Special | ClusterFS,
+
+ SpecialClusterSofs = Special | ClusterSofs,
+
+ SpecialClusterDfs = Special | ClusterDfs,
+
+ TemporaryDisktree = Temporary | Disktree,
+
+ TemporaryPrintQueue = Temporary | PrintQueue,
+
+ TemporaryDevice = Temporary | Device,
+
+ TemporaryIpc = Temporary | Ipc,
+
+ TemporaryClusterFS = Temporary | ClusterFS,
+
+ TemporaryClusterSofs = Temporary | ClusterSofs,
+
+ TemporaryClusterDfs = Temporary | ClusterDfs,
+
+ SpecialTemporaryDisktree = Special | Temporary | Disktree,
+
+ SpecialTemporaryPrintQueue = Special | Temporary | PrintQueue,
+
+ SpecialTemporaryDevice = Special | Temporary | Device,
+
+ SpecialTemporaryIpc = Special | Temporary | Ipc,
+
+ SpecialTemporaryClusterFS = Special | Temporary | ClusterFS,
+
+ SpecialTemporaryClusterSofs = Special | Temporary | ClusterSofs,
+
+ SpecialTemporaryClusterDfs = Special | Temporary | ClusterDfs,
+
+ SpecialTemporary = Special | Temporary,
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs
new file mode 100644
index 0000000..6608fe6
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Dionach.ShareAudit.Modules.Services.Interop
+{
+ internal enum SID_NAME_USE
+ {
+ SidTypeUser = 1,
+ SidTypeGroup,
+ SidTypeDomain,
+ SidTypeAlias,
+ SidTypeWellKnownGroup,
+ SidTypeDeletedAccount,
+ SidTypeInvalid,
+ SidTypeUnknown,
+ SidTypeComputer
+ }
+
+ internal partial class NativeMethods
+ {
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true, SetLastError = true)]
+ internal static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid);
+
+ [DllImport("advapi32.dll", EntryPoint = "GetLengthSid", CharSet = CharSet.Auto)]
+ internal static extern int GetLengthSid(IntPtr pSID);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true, SetLastError = true)]
+ internal static extern bool LookupAccountSid(
+ string lpSystemName,
+ [MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
+ System.Text.StringBuilder lpName,
+ ref uint cchName,
+ System.Text.StringBuilder ReferencedDomainName,
+ ref uint cchReferencedDomainName,
+ out SID_NAME_USE peUse);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj b/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj
new file mode 100644
index 0000000..f84aa26
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj
@@ -0,0 +1,51 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8425D05F-F3F4-4132-9BE1-BED752685333}
+ Library
+ Properties
+ Dionach.ShareAudit.Modules.Services.Interop
+ Dionach.ShareAudit.Modules.Services.Interop
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs
new file mode 100644
index 0000000..b4c7bd5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs
@@ -0,0 +1,59 @@
+using System.Runtime.InteropServices;
+
+namespace Dionach.ShareAudit.Modules.Services.Interop
+{
+ internal enum ResourceDisplaytype : int
+ {
+ Generic = 0x0,
+ Domain = 0x01,
+ Server = 0x02,
+ Share = 0x03,
+ File = 0x04,
+ Group = 0x05,
+ Network = 0x06,
+ Root = 0x07,
+ Shareadmin = 0x08,
+ Directory = 0x09,
+ Tree = 0x0a,
+ Ndscontainer = 0x0b
+ }
+
+ internal enum ResourceScope : int
+ {
+ Connected = 1,
+ GlobalNetwork,
+ Remembered,
+ Recent,
+ Context
+ }
+
+ internal enum ResourceType : int
+ {
+ Any = 0,
+ Disk = 1,
+ Print = 2,
+ Reserved = 8,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct NetResource
+ {
+ internal ResourceScope Scope;
+ internal ResourceType ResourceType;
+ internal ResourceDisplaytype DisplayType;
+ internal int Usage;
+ internal string LocalName;
+ internal string RemoteName;
+ internal string Comment;
+ internal string Provider;
+ }
+
+ internal partial class NativeMethods
+ {
+ [DllImport("mpr.dll")]
+ internal static extern int WNetAddConnection2(ref NetResource netResource, string password, string username, uint flags);
+
+ [DllImport("mpr.dll")]
+ internal static extern int WNetCancelConnection2(string name, int flags, bool force);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs
new file mode 100644
index 0000000..bfd4df1
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Dionach.ShareAudit.Modules.Services.Interop
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SHARE_INFO_1
+ {
+ [MarshalAs(UnmanagedType.LPWStr)]
+ internal string shi1_netname;
+
+ internal uint shi1_type;
+
+ [MarshalAs(UnmanagedType.LPWStr)]
+ internal string shi1_remark;
+ }
+
+ internal partial class NativeMethods
+ {
+ [DllImport("Netapi32.dll")]
+ internal static extern int NetApiBufferFree(IntPtr Buffer);
+
+ [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int NetShareEnum(
+ string ServerName,
+ int level,
+ ref IntPtr bufPtr,
+ uint prefmaxlen,
+ ref int entriesread,
+ ref int totalentries,
+ ref int resume_handle);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..46c5f0e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following set of attributes.
+// Change these attribute values to modify the information associated with an assembly.
+[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.Services.Interop")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.Services.Interop")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you
+// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8425d05f-f3f4-4132-9be1-bed752685333")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version Minor Version Build Number Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers by using the '*'
+// as shown below: [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: InternalsVisibleTo("Dionach.ShareAudit.Modules.Services")]
diff --git a/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs
new file mode 100644
index 0000000..6d25ef8
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs
@@ -0,0 +1,93 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.DirectoryServices.AccountManagement;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class CredentialsValidationService : ICredentialsValidationService
+ {
+ public async Task<(string domain, string username)> GetCurrentUserInformation()
+ {
+ return await Task.Run(() =>
+ {
+ return (domain: Environment.UserDomainName, username: Environment.UserName);
+ });
+ }
+
+ public async Task<(bool isValid, string errorMessage)> ValidateCredentialsAsync(Credentials credentials)
+ {
+ return await Task.Run(() =>
+ {
+ var result = (isValid: true, errorMessage: string.Empty);
+
+ try
+ {
+ using (var context = new PrincipalContext(ContextType.Domain, credentials.Domain))
+ {
+ if (!context.ValidateCredentials(credentials.Username, credentials.Password))
+ {
+ result.isValid = false;
+ result.errorMessage = "Domain, user name, or password are incorrect";
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ result.isValid = false;
+ result.errorMessage = $"Credentials could not be validated: {ex.Message}";
+ }
+
+ return result;
+ });
+ }
+
+ public async Task<(bool isValid, string errorMessage)> ValidateDomainAsync(string domain)
+ {
+ return await Task.Run(() =>
+ {
+ var result = (isValid: true, errorMessage: string.Empty);
+
+ if (string.IsNullOrWhiteSpace(domain))
+ {
+ result.isValid = false;
+ result.errorMessage = "Domain cannot be empty";
+ }
+
+ return result;
+ });
+ }
+
+ public async Task<(bool isValid, string errorMessage)> ValidatePasswordAsync(string password)
+ {
+ return await Task.Run(() =>
+ {
+ var result = (isValid: true, errorMessage: string.Empty);
+
+ if (string.IsNullOrWhiteSpace(password))
+ {
+ result.isValid = false;
+ result.errorMessage = "Password cannot be empty";
+ }
+
+ return result;
+ });
+ }
+
+ public async Task<(bool isValid, string errorMessage)> ValidateUsernameAsync(string username)
+ {
+ return await Task.Run(() =>
+ {
+ var result = (isValid: true, errorMessage: string.Empty);
+
+ if (string.IsNullOrWhiteSpace(username))
+ {
+ result.isValid = false;
+ result.errorMessage = "User name cannot be empty";
+ }
+
+ return result;
+ });
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj b/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj
new file mode 100644
index 0000000..0523d48
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj
@@ -0,0 +1,134 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}
+ library
+ Properties
+ Dionach.ShareAudit.Modules.Services
+ Dionach.ShareAudit.Modules.Services
+ v4.6.1
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ PackageReference
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}
+ Dionach.ShareAudit.Model
+
+
+ {8425D05F-F3F4-4132-9BE1-BED752685333}
+ Dionach.ShareAudit.Modules.Services.Interop
+
+
+
+
+ 2.4.0.99
+
+
+ 7.1.0.431
+
+
+
+ 3.0.0
+
+
+ 4.5.0
+
+
+ 4.5.0
+
+
+ 4.5.0
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs
new file mode 100644
index 0000000..3acf447
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class DnsUtilitiesService : IDnsUtilitiesService
+ {
+ public string GetPtrRecord(IPAddress ipAddress)
+ {
+ try
+ {
+ var ptrRecord = Dns.GetHostEntry(ipAddress).HostName;
+ if (TryResolveHost(ptrRecord, out var resolvedIPAddresses))
+ {
+ if (resolvedIPAddresses.Contains(ipAddress))
+ {
+ return ptrRecord;
+ }
+ }
+ }
+ catch (SocketException)
+ {
+ }
+
+ return null;
+ }
+
+ public bool TryResolveHost(string host, out IEnumerable ipAddresses)
+ {
+ host = host ?? throw new ArgumentNullException(nameof(host));
+
+ try
+ {
+ ipAddresses = Dns.GetHostAddresses(host);
+ return true;
+ }
+ catch (SocketException ex) when (ex.Message == "No such host is known")
+ {
+ ipAddresses = Array.Empty();
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs b/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs
new file mode 100644
index 0000000..e9ac5a3
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs
@@ -0,0 +1,256 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.AccessControl;
+using System.Security.Principal;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class FileSystemOperationService : IFileSystemOperationService
+ {
+ private readonly ISidUtilitiesService _sidUtilitiesService;
+
+ public FileSystemOperationService(ISidUtilitiesService sidUtilitiesService)
+ {
+ _sidUtilitiesService = sidUtilitiesService ?? throw new ArgumentNullException(nameof(sidUtilitiesService));
+ }
+
+ public IEnumerable EnumerateAccessRules(string path)
+ {
+ FileAttributes attributes = FileAttributes.Normal;
+ try
+ {
+ attributes = File.GetAttributes(path);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ catch (IOException)
+ {
+ }
+
+ AuthorizationRuleCollection authorizationRuleCollection = null;
+
+ if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
+ {
+ try
+ {
+ authorizationRuleCollection = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+ else
+ {
+ try
+ {
+ authorizationRuleCollection = File.GetAccessControl(path).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ catch (UnauthorizedAccessException)
+ {
+ }
+ }
+
+ if (authorizationRuleCollection != null)
+ {
+ foreach (AccessRule authorizationRule in authorizationRuleCollection)
+ {
+ string identity = _sidUtilitiesService.SidStringToAccountName(path.TrimStart("\\\\".ToCharArray()).Split('\\')[0], authorizationRule.IdentityReference.Value);
+ string rights = (authorizationRule as FileSystemAccessRule).FileSystemRights.ToString();
+
+ // Handle rights that .NET decided not to include in the enum http://stackoverflow.com/questions/9694834/encountering-a-filesystemrights-value-that-isnt-defined-in-enumeration
+ switch (rights)
+ {
+ case "268435456":
+ rights = "FullControl";
+ break;
+
+ case "-536805376":
+ rights = "Modify, Synchronize";
+ break;
+
+ case "-1610612736":
+ rights = "ReadAndExecute, Synchronize";
+ break;
+ }
+
+ yield return new AccessRuleEntry
+ {
+ Identity = identity,
+ Rights = rights,
+ Type = authorizationRule.AccessControlType,
+ Inherited = authorizationRule.IsInherited,
+ };
+ }
+ }
+ }
+
+ public IEnumerable EnumerateDirectories(string path)
+ {
+ try
+ {
+ return Directory.EnumerateDirectories(path).Select(x => new DirectoryInfo(x)).Select(x => new DirectoryEntry
+ {
+ Name = x.Name,
+ FullName = x.FullName
+ });
+ }
+ catch (IOException)
+ {
+ return Enumerable.Empty();
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return Enumerable.Empty();
+ }
+ }
+
+ public IEnumerable EnumerateFiles(string path)
+ {
+ try
+ {
+ return Directory.EnumerateFiles(path).Select(x => new FileInfo(x)).Select(x => new FileEntry
+ {
+ Name = x.Name,
+ FullName = x.FullName
+ });
+ }
+ catch (IOException)
+ {
+ return Enumerable.Empty();
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return Enumerable.Empty();
+ }
+ }
+
+ public EffectiveAccess GetEffectiveAccess(string path)
+ {
+ var effectiveAccess = new EffectiveAccess { Read = true, Write = true };
+ bool isDirectory;
+
+ try
+ {
+ isDirectory = File.GetAttributes(path).HasFlag(FileAttributes.Directory);
+ }
+ catch (IOException)
+ {
+ return new EffectiveAccess { Read = false, Write = false };
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return new EffectiveAccess { Read = false, Write = false };
+ }
+
+ if (isDirectory)
+ {
+ try
+ {
+ Directory.GetFileSystemEntries(path);
+ }
+ catch (IOException)
+ {
+ effectiveAccess.Read = false;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ effectiveAccess.Read = false;
+ }
+
+ if (effectiveAccess.Read)
+ {
+ // create a FileSecurity object allowing full control so that we can be sure we
+ // have permissions to delete the file
+ var fileSecurity = new FileSecurity();
+ fileSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, AccessControlType.Allow));
+
+ try
+ {
+ // create the file in a using statement to allow the FileStream, which
+ // implements IDisposable, to be closed on completion
+ using (File.Create(path + "\\write-test.shareaudit", 1, FileOptions.DeleteOnClose, fileSecurity))
+ {
+ }
+ }
+ catch (IOException)
+ {
+ effectiveAccess.Write = false;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ effectiveAccess.Write = false;
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ using (File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ }
+ }
+ catch (IOException)
+ {
+ effectiveAccess.Read = false;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ effectiveAccess.Read = false;
+ }
+
+ if (effectiveAccess.Read)
+ {
+ try
+ {
+ using (File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
+ {
+ }
+ }
+ catch (IOException)
+ {
+ effectiveAccess.Write = false;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ effectiveAccess.Write = false;
+ }
+ }
+ }
+
+ return effectiveAccess;
+ }
+
+ public string HeadFile(string path, int bytes)
+ {
+ try
+ {
+ var bufferSize = Math.Min(bytes, new FileInfo(path).Length);
+ var buffer = new byte[bufferSize];
+ using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
+ {
+ fs.Read(buffer, 0, buffer.Length);
+ return Convert.ToBase64String(buffer);
+ }
+ }
+ catch (IOException)
+ {
+ return default;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return default;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs b/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs
new file mode 100644
index 0000000..6e8d6d5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs
@@ -0,0 +1,95 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class FileSystemStoreService : IFileSystemStoreService
+ {
+ public string ExportDefaultFilename => DateTime.Now.ToString("yyyyMMddHHmm", CultureInfo.InvariantCulture);
+
+ public string ExportFilter => "Share Audit Export (*.csv)|*.csv";
+
+ public string ShareAuditDefaultFilename => DateTime.Now.ToString("yyyyMMddHHmm", CultureInfo.InvariantCulture);
+
+ public string ShareAuditFilter => "Share Audit File (*.shareaudit)|*.shareaudit";
+
+ public async Task CreateProjectAsync(string path)
+ {
+ await SaveProjectAsync(new Project(), path);
+ }
+
+ public async Task ExportProjectAsync(Project project, string path)
+ {
+ await Task.Run(() =>
+ {
+ var username = $"{project.Configuration.Credentials.Username}@{project.Configuration.Credentials.Domain}";
+ var sb = new StringBuilder();
+ sb.AppendLine("\"UNC Path\",\"Type\",\"Accessible\",\"Effective Read\",\"Effective Write\",\"Username\"");
+
+ foreach (var host in project.Hosts)
+ {
+ sb.AppendLine($"\"\\\\{host.Name}\",\"Host\",\"{host.Accessible}\",\"N/A\",\"N/A\",\"{username}\"");
+
+ foreach (var share in host.Shares)
+ {
+ WriteFolderEntry(sb, share, username);
+ }
+ }
+
+ File.WriteAllText(path, sb.ToString());
+ });
+ }
+
+ public async Task LoadProjectAsync(string path)
+ {
+ return await Task.Run(() =>
+ {
+ using (var reader = File.OpenRead(path))
+ {
+ var serializer = new XmlSerializer(typeof(Project));
+ return serializer.Deserialize(reader) as Project;
+ }
+ });
+ }
+
+ public async Task SaveProjectAsync(Project project, string path)
+ {
+ await Task.Run(() =>
+ {
+ var settings = new XmlWriterSettings
+ {
+ Indent = true
+ };
+
+ using (var writer = XmlWriter.Create(path, settings))
+ {
+ var serializer = new XmlSerializer(typeof(Project));
+ serializer.Serialize(writer, project);
+ }
+ });
+ }
+
+ private void WriteFolderEntry(StringBuilder sb, IFolderEntry entry, string username)
+ {
+ sb.AppendLine($"\"{entry.FullName}\",\"{((entry is Share) ? "Share" : "Directory")}\",\"{((entry is Share) ? (entry as Share).Accessible : entry.EffectiveAccess.Read)}\",\"{entry.EffectiveAccess.Read}\",\"{entry.EffectiveAccess.Write}\",\"{username}\"");
+
+ foreach (var childEntry in entry.FileSystemEntries)
+ {
+ if (childEntry is IFolderEntry)
+ {
+ WriteFolderEntry(sb, childEntry as IFolderEntry, username);
+ }
+ else
+ {
+ sb.AppendLine($"\"{childEntry.FullName}\",\"File\",\"{childEntry.EffectiveAccess.Read}\",\"{childEntry.EffectiveAccess.Read}\",\"{childEntry.EffectiveAccess.Write}\",\"{username}\"");
+ }
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs
new file mode 100644
index 0000000..01ca2bd
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs
@@ -0,0 +1,18 @@
+using Dionach.ShareAudit.Model;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface ICredentialsValidationService
+ {
+ Task<(string domain, string username)> GetCurrentUserInformation();
+
+ Task<(bool isValid, string errorMessage)> ValidateCredentialsAsync(Credentials credentials);
+
+ Task<(bool isValid, string errorMessage)> ValidateDomainAsync(string domain);
+
+ Task<(bool isValid, string errorMessage)> ValidatePasswordAsync(string password);
+
+ Task<(bool isValid, string errorMessage)> ValidateUsernameAsync(string username);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs
new file mode 100644
index 0000000..4eae76c
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Net;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IDnsUtilitiesService
+ {
+ string GetPtrRecord(IPAddress ipAddress);
+
+ bool TryResolveHost(string host, out IEnumerable ipAddresses);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs b/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs
new file mode 100644
index 0000000..a039ee4
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs
@@ -0,0 +1,18 @@
+using Dionach.ShareAudit.Model;
+using System.Collections.Generic;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IFileSystemOperationService
+ {
+ IEnumerable EnumerateAccessRules(string path);
+
+ IEnumerable EnumerateDirectories(string path);
+
+ IEnumerable EnumerateFiles(string path);
+
+ EffectiveAccess GetEffectiveAccess(string path);
+
+ string HeadFile(string path, int bytes);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs b/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs
new file mode 100644
index 0000000..1ba77b4
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs
@@ -0,0 +1,24 @@
+using Dionach.ShareAudit.Model;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IFileSystemStoreService
+ {
+ string ExportDefaultFilename { get; }
+
+ string ExportFilter { get; }
+
+ string ShareAuditDefaultFilename { get; }
+
+ string ShareAuditFilter { get; }
+
+ Task CreateProjectAsync(string path);
+
+ Task ExportProjectAsync(Project project, string path);
+
+ Task LoadProjectAsync(string path);
+
+ Task SaveProjectAsync(Project project, string path);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs b/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs
new file mode 100644
index 0000000..f7d63ee
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs
@@ -0,0 +1,12 @@
+using Dionach.ShareAudit.Model;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IImportScopeFromActiveDirectoryService
+ {
+ Task Import(string domain, string username, string password, ImportComputerType importComputerType);
+
+ Task Import(string domain, ImportComputerType importComputerType);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs b/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs
new file mode 100644
index 0000000..dda296b
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs
@@ -0,0 +1,7 @@
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IPortScanService
+ {
+ bool IsTcpPortOpen(string host, ushort port, int millisecondsTimeout);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs
new file mode 100644
index 0000000..55e0a3f
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using System.Net;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IScopeExpansionService
+ {
+ IEnumerable ExpandScopeToIPAddresses(string scope);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs
new file mode 100644
index 0000000..fc29854
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IScopeNormalizationService
+ {
+ Task NormalizeScopeAsync(string scope);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs
new file mode 100644
index 0000000..49c8d37
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IScopeValidationService
+ {
+ Task<(bool isValid, string errorMessage)> ValidateScopeAsync(string scope);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs b/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs
new file mode 100644
index 0000000..53d8a78
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs
@@ -0,0 +1,16 @@
+using Dionach.ShareAudit.Model;
+using System;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface IShareAuditService
+ {
+ event EventHandler Started;
+
+ event EventHandler Stopped;
+
+ void StartAudit(Project project);
+
+ void StopAudit();
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs
new file mode 100644
index 0000000..0084273
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs
@@ -0,0 +1,7 @@
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface ISidUtilitiesService
+ {
+ string SidStringToAccountName(string host, string sid);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs
new file mode 100644
index 0000000..3a3c65e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public interface ISmbUtilitiesService
+ {
+ NetUseConnection CreateNetUseConnection(string host, string username, string domain, string password);
+
+ IEnumerable NetShareEnum(string host);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs b/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs
new file mode 100644
index 0000000..7f3ff2e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs
@@ -0,0 +1,100 @@
+using Dionach.ShareAudit.Model;
+using System.Collections.Generic;
+using System.DirectoryServices;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ImportScopeFromActiveDirectoryService : IImportScopeFromActiveDirectoryService
+ {
+ public async Task Import(string domain, string username, string password, ImportComputerType importComputerType)
+ {
+ return await Task.Run(() =>
+ {
+ var computerNames = new List();
+
+ using (var directoryEntry = new System.DirectoryServices.DirectoryEntry($"LDAP://{domain}", username, password))
+ {
+ using (var directorySearcher = new DirectorySearcher(directoryEntry))
+ {
+ switch (importComputerType)
+ {
+ case ImportComputerType.Servers:
+ directorySearcher.Filter = "(&(objectClass=computer)(operatingSystem=Windows Server*))";
+ break;
+
+ case ImportComputerType.Workstations:
+ directorySearcher.Filter = "(&(objectClass=computer)(|(operatingSystem=Windows XP*)(operatingSystem=Windows Vista*)(operatingSystem=Windows 7*)(operatingSystem=Windows 8*)(operatingSystem=Windows 10*)))";
+ break;
+
+ case ImportComputerType.All:
+ directorySearcher.Filter = "(objectClass=computer)";
+ break;
+ }
+
+ directorySearcher.SizeLimit = 0;
+ directorySearcher.PageSize = 250;
+ directorySearcher.PropertiesToLoad.Add("dNSHostName");
+
+ using (var searchResultCollection = directorySearcher.FindAll())
+ {
+ foreach (SearchResult searchResult in searchResultCollection)
+ {
+ if (searchResult.Properties["dNSHostName"].Count > 0)
+ {
+ computerNames.Add(searchResult.Properties["dNSHostName"][0] as string);
+ }
+ }
+ }
+ }
+ }
+ return string.Join(", ", computerNames);
+ });
+ }
+
+ public async Task Import(string domain, ImportComputerType importComputerType)
+ {
+ return await Task.Run(() =>
+ {
+ var computerNames = new List();
+
+ using (var directoryEntry = new System.DirectoryServices.DirectoryEntry($"LDAP://{domain}"))
+ {
+ using (var directorySearcher = new DirectorySearcher(directoryEntry))
+ {
+ switch (importComputerType)
+ {
+ case ImportComputerType.Servers:
+ directorySearcher.Filter = "(&(objectClass=computer)(operatingSystem=Windows Server*))";
+ break;
+
+ case ImportComputerType.Workstations:
+ directorySearcher.Filter = "(&(objectClass=computer)(|(operatingSystem=Windows XP*)(operatingSystem=Windows Vista*)(operatingSystem=Windows 7*)(operatingSystem=Windows 8*)(operatingSystem=Windows 10*)))";
+ break;
+
+ case ImportComputerType.All:
+ directorySearcher.Filter = "(objectClass=computer)";
+ break;
+ }
+
+ directorySearcher.SizeLimit = 0;
+ directorySearcher.PageSize = 250;
+ directorySearcher.PropertiesToLoad.Add("dNSHostName");
+
+ using (var searchResultCollection = directorySearcher.FindAll())
+ {
+ foreach (SearchResult searchResult in searchResultCollection)
+ {
+ if (searchResult.Properties["dNSHostName"].Count > 0)
+ {
+ computerNames.Add(searchResult.Properties["dNSHostName"][0] as string);
+ }
+ }
+ }
+ }
+ }
+ return string.Join(", ", computerNames);
+ });
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs b/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs
new file mode 100644
index 0000000..18c1d6c
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs
@@ -0,0 +1,76 @@
+using Dionach.ShareAudit.Modules.Services.Interop;
+using System;
+using System.ComponentModel;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class NetUseConnection : IDisposable
+ {
+ private readonly string _networkName;
+
+ private bool _disposedValue = false;
+
+ public NetUseConnection(string host, string username, string domain, string password)
+ {
+ const int ERROR_SESSION_CREDENTIAL_CONFLICT = 1219;
+ const int ERROR_SUCCESS = 0;
+ const int ERROR_NETWORK_PATH_NOT_FOUND = 53;
+
+ _networkName = "\\\\" + host;
+ var netResource = new NetResource()
+ {
+ Scope = ResourceScope.GlobalNetwork,
+ ResourceType = ResourceType.Any,
+ DisplayType = ResourceDisplaytype.Generic,
+ RemoteName = _networkName
+ };
+
+ var result = NativeMethods.WNetAddConnection2(
+ ref netResource,
+ password,
+ $"{domain}\\{username}",
+ 0);
+
+ // If there is already a net connection, cancel it and try again
+ if (result == ERROR_SESSION_CREDENTIAL_CONFLICT)
+ {
+ NativeMethods.WNetCancelConnection2(_networkName, 0, true);
+ result = NativeMethods.WNetAddConnection2(
+ ref netResource,
+ password,
+ $"{domain}\\{username}",
+ 0);
+ }
+
+ if (!(result == ERROR_SUCCESS || result == ERROR_SESSION_CREDENTIAL_CONFLICT || result == ERROR_NETWORK_PATH_NOT_FOUND))
+ {
+ throw new Win32Exception((int)result);
+ }
+ }
+
+ ~NetUseConnection()
+ {
+ Dispose(false);
+ }
+
+ void IDisposable.Dispose()
+ {
+ Dispose(true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ NativeMethods.WNetCancelConnection2(_networkName, 0, true);
+ }
+
+ _disposedValue = true;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs b/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs
new file mode 100644
index 0000000..ec419bb
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs
@@ -0,0 +1,31 @@
+using System.Net.Sockets;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class PortScanService : IPortScanService
+ {
+ public bool IsTcpPortOpen(string host, ushort port, int millisecondsTimeout)
+ {
+ try
+ {
+ using (var client = new TcpClient())
+ {
+ var result = client.BeginConnect(host, port, null, null);
+ var success = result.AsyncWaitHandle.WaitOne(millisecondsTimeout);
+ if (!success)
+ {
+ return false;
+ }
+
+ client.EndConnect(result);
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c1a0589
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following set of attributes.
+// Change these attribute values to modify the information associated with an assembly.
+[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.Services")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.Services")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you
+// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// In order to begin building localizable applications, set
+// CultureYouAreCodingWith in your .csproj file inside a . For
+// example, if you are using US english in your source files, set the to en-US. Then
+// uncomment the NeutralResourceLanguage attribute below. Update the "en-US" in the line below to
+// match the UICulture setting in the project file.
+[assembly: NeutralResourcesLanguage("en-GB"/*, UltimateResourceFallbackLocation.Satellite*/)]
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
+]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version Minor Version Build Number Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers by using the '*'
+// as shown below: [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: CLSCompliant(false)]
diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..a548489
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Modules.Services.Properties
+{
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if (object.ReferenceEquals(resourceMan, null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dionach.ShareAudit.Modules.Services.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..d27f270
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Modules.Services.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs
new file mode 100644
index 0000000..abfb091
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ScopeExpansionService : IScopeExpansionService
+ {
+ private readonly IDnsUtilitiesService _dnsUtilitiesService;
+
+ public ScopeExpansionService(
+ IDnsUtilitiesService dnsUtilitiesService)
+ {
+ _dnsUtilitiesService = dnsUtilitiesService ?? throw new ArgumentNullException(nameof(dnsUtilitiesService));
+ }
+
+ public IEnumerable ExpandScopeToIPAddresses(string scope)
+ {
+ scope = scope ?? throw new ArgumentNullException(nameof(scope));
+ scope = scope.Replace(" ", string.Empty);
+ scope = scope.Replace("\t", string.Empty);
+
+ var lines = scope.Split(",;\r\n".ToCharArray());
+
+ var scopeIPAddresses = new HashSet();
+
+ foreach (var line in lines)
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ {
+ if (line.Contains("/") && IPNetwork.TryParse(line, out var ipNetwork))
+ {
+ using (var ipAddresses = ipNetwork.ListIPAddress())
+ {
+ foreach (var ipAddress in ipAddresses)
+ {
+ if (ipAddress.AddressFamily == AddressFamily.InterNetwork && (ipAddress.Equals(ipNetwork.Network) || ipAddress.Equals(ipNetwork.Broadcast)))
+ {
+ continue;
+ }
+
+ scopeIPAddresses.Add(ipAddress);
+ }
+ }
+ }
+ else if (IPAddress.TryParse(line, out var ipAddress))
+ {
+ scopeIPAddresses.Add(ipAddress);
+ }
+ else if (_dnsUtilitiesService.TryResolveHost(line, out var ipAddresses))
+ {
+ foreach (var address in ipAddresses)
+ {
+ scopeIPAddresses.Add(address);
+ }
+ }
+ }
+ }
+
+ return scopeIPAddresses;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs
new file mode 100644
index 0000000..220d5f9
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ScopeNormalizationService : IScopeNormalizationService
+ {
+ public async Task NormalizeScopeAsync(string scope)
+ {
+ return await Task.Run(() =>
+ {
+ scope = scope ?? throw new ArgumentNullException(nameof(scope));
+ scope = scope.Replace(" ", string.Empty);
+ scope = scope.Replace("\t", string.Empty);
+
+ var lines = scope.Split(",;\r\n".ToCharArray());
+
+ return string.Join(", ", lines.Where(x => !string.IsNullOrWhiteSpace(x)));
+ });
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs
new file mode 100644
index 0000000..2e9b654
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ScopeValidationService : IScopeValidationService
+ {
+ public async Task<(bool isValid, string errorMessage)> ValidateScopeAsync(string scope)
+ {
+ return await Task.Run(() =>
+ {
+ var result = (isValid: true, errorMessage: string.Empty);
+
+ if (string.IsNullOrWhiteSpace(scope))
+ {
+ result.isValid = false;
+ result.errorMessage = "Scope cannot be empty";
+ }
+
+ return result;
+ });
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs b/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs
new file mode 100644
index 0000000..54a5175
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs
@@ -0,0 +1,28 @@
+using Prism.Ioc;
+using Prism.Modularity;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ServicesModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ containerRegistry.RegisterSingleton();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs b/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs
new file mode 100644
index 0000000..59eea72
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs
@@ -0,0 +1,481 @@
+using Dionach.ShareAudit.Model;
+using SimpleImpersonation;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Net;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public sealed class ShareAuditService : IShareAuditService, IDisposable, INotifyPropertyChanged
+ {
+ private readonly BackgroundWorker _backgroundWorker;
+
+ private readonly IDnsUtilitiesService _dnsUtilitiesService;
+
+ private readonly IFileSystemOperationService _fileSystemOperationService;
+
+ private readonly ParallelOptions _parallelOptions = new ParallelOptions
+ {
+#if DEBUG
+ //MaxDegreeOfParallelism = 1
+#endif
+ };
+
+ private readonly IPortScanService _portScanService;
+ private readonly IScopeExpansionService _scopeExpansionService;
+ private readonly ISmbUtilitiesService _smbUtilitiesService;
+ private bool _disposedValue = false;
+ private bool _isBusy;
+ private Project _project;
+ private SynchronizationContext _uiContext;
+
+ public ShareAuditService(
+ IScopeExpansionService scopeExpansionService,
+ IDnsUtilitiesService dnsUtilitiesService,
+ IPortScanService portScanService,
+ ISmbUtilitiesService smbUtilitiesService,
+ IFileSystemOperationService fileSystemOperationService)
+ {
+ _scopeExpansionService = scopeExpansionService ?? throw new ArgumentNullException(nameof(scopeExpansionService));
+ _dnsUtilitiesService = dnsUtilitiesService ?? throw new ArgumentNullException(nameof(dnsUtilitiesService));
+ _portScanService = portScanService ?? throw new ArgumentNullException(nameof(portScanService));
+ _smbUtilitiesService = smbUtilitiesService ?? throw new ArgumentNullException(nameof(smbUtilitiesService));
+ _fileSystemOperationService = fileSystemOperationService ?? throw new ArgumentNullException(nameof(fileSystemOperationService));
+
+ _backgroundWorker = new BackgroundWorker
+ {
+ WorkerSupportsCancellation = true
+ };
+
+ _backgroundWorker.DoWork += DoWork;
+ _backgroundWorker.RunWorkerCompleted += RunWorkerCompleted;
+ }
+
+ ~ShareAuditService()
+ {
+ Dispose(false);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public event EventHandler Started;
+
+ public event EventHandler Stopped;
+
+ public bool IsBusy
+ {
+ get => _isBusy;
+ private set
+ {
+ if (value == _isBusy)
+ {
+ return;
+ }
+
+ _isBusy = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void StartAudit(Project project)
+ {
+ if (!_backgroundWorker.IsBusy)
+ {
+ _uiContext = SynchronizationContext.Current;
+ _project = project;
+ _backgroundWorker.RunWorkerAsync();
+ }
+ }
+
+ public void StopAudit()
+ {
+ if (_backgroundWorker.IsBusy)
+ {
+ _backgroundWorker.CancelAsync();
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _backgroundWorker.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ private void DoAuthenticatedHostWork(Host host)
+ {
+ if (host.State == HostState.EnumeratingShares && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => host.Shares = new ObservableCollection(), null);
+
+ foreach (var shareInfo1 in _smbUtilitiesService.NetShareEnum(host.Name))
+ {
+ if ((shareInfo1.Type & ShareTypes.Ipc) == ShareTypes.Ipc)
+ {
+ continue;
+ }
+
+ _uiContext.Send(
+ (_) =>
+ {
+ host.Shares.Add(new Share
+ {
+ Name = shareInfo1.NetName,
+ FullName = $"\\\\{host.Name}\\{shareInfo1.NetName}",
+ Type = shareInfo1.Type,
+ Remark = shareInfo1.Remark
+ });
+ }, null);
+ }
+
+ _uiContext.Send((_) => host.State = HostState.AuditingShares, null);
+ }
+
+ if ((host.State == HostState.AuditingShares || host.State == HostState.NestedAuditingSuspended) && !_backgroundWorker.CancellationPending)
+ {
+ Parallel.ForEach(host.Shares, _parallelOptions, (share) =>
+ {
+ if (!_backgroundWorker.CancellationPending)
+ {
+ if (share.Type.HasFlag(ShareTypes.PrintQueue))
+ {
+ _uiContext.Send((_) => share.State = FolderEntryState.Complete, null);
+ }
+ else
+ {
+ DoFolderEntryWork(share);
+ }
+ }
+ });
+
+ if (host.Shares.All(x => x.State == FolderEntryState.Complete))
+ {
+ _uiContext.Send((_) => host.State = HostState.Complete, null);
+ }
+ else if (host.Shares.All(x => x.State == FolderEntryState.Complete || x.State == FolderEntryState.NestedAuditingSuspended))
+ {
+ _uiContext.Send((_) => host.State = HostState.NestedAuditingSuspended, null);
+ }
+ }
+ }
+
+ private void DoFileEntryWork(FileEntry fileEntry)
+ {
+ if (fileEntry.State == FileEntryState.New && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => fileEntry.State = FileEntryState.EnumeratingAcls, null);
+ }
+
+ if (fileEntry.State == FileEntryState.EnumeratingAcls && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => fileEntry.AccessRules = new ObservableCollection(), null);
+
+ foreach (var accessRule in _fileSystemOperationService.EnumerateAccessRules(fileEntry.FullName))
+ {
+ _uiContext.Send((_) => fileEntry.AccessRules.Add(accessRule), null);
+ }
+
+ _uiContext.Send((_) => fileEntry.State = FileEntryState.GettingEffectiveAccess, null);
+ }
+
+ if (fileEntry.State == FileEntryState.GettingEffectiveAccess && !_backgroundWorker.CancellationPending)
+ {
+ var effectiveAccess = _fileSystemOperationService.GetEffectiveAccess(fileEntry.FullName);
+ _uiContext.Send((_) => fileEntry.EffectiveAccess = effectiveAccess, null);
+
+ _uiContext.Send((_) => fileEntry.State = FileEntryState.ReadingHead, null);
+ }
+
+ if (fileEntry.State == FileEntryState.ReadingHead && !_backgroundWorker.CancellationPending)
+ {
+ if (fileEntry.EffectiveAccess.Read)
+ {
+ var head = _fileSystemOperationService.HeadFile(fileEntry.FullName, 1024);
+ _uiContext.Send((_) => fileEntry.Head = head, null);
+ }
+
+ _uiContext.Send((_) => fileEntry.State = FileEntryState.Complete, null);
+ }
+ }
+
+ private void DoFolderEntryWork(IFolderEntry folderEntry)
+ {
+ var level = folderEntry.FullName.Trim('\\').Split('\\').Count() - 1;
+
+ if (folderEntry.State == FolderEntryState.New && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumeratingAcls, null);
+ }
+
+ if (folderEntry.State == FolderEntryState.EnumeratingAcls && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => folderEntry.AccessRules = new ObservableCollection(), null);
+
+ foreach (var accessRule in _fileSystemOperationService.EnumerateAccessRules(folderEntry.FullName))
+ {
+ _uiContext.Send((_) => folderEntry.AccessRules.Add(accessRule), null);
+ }
+
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.GettingEffectiveAccess, null);
+ }
+
+ if (folderEntry.State == FolderEntryState.GettingEffectiveAccess && !_backgroundWorker.CancellationPending)
+ {
+ var effectiveAccess = _fileSystemOperationService.GetEffectiveAccess(folderEntry.FullName);
+ _uiContext.Send((_) => folderEntry.EffectiveAccess = effectiveAccess, null);
+
+ if (level > 1 && folderEntry is DirectoryEntry)
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumerationSuspended, null);
+ }
+ else
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumeratingFilesystemEntries, null);
+ }
+ }
+
+ if (folderEntry.State == FolderEntryState.EnumeratingFilesystemEntries && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => folderEntry.FileSystemEntries = new ObservableCollection(), null);
+
+ var directories = Enumerable.Empty();
+
+ foreach (var directory in _fileSystemOperationService.EnumerateDirectories(folderEntry.FullName))
+ {
+ _uiContext.Send((_) => { folderEntry.FileSystemEntries.Add(directory); }, null);
+ }
+
+ foreach (var files in _fileSystemOperationService.EnumerateFiles(folderEntry.FullName))
+ {
+ _uiContext.Send((_) => { folderEntry.FileSystemEntries.Add(files); }, null);
+ }
+
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.AuditingFileSystemEntries, null);
+ }
+
+ if (folderEntry.State == FolderEntryState.AuditingFileSystemEntries && !_backgroundWorker.CancellationPending)
+ {
+ Parallel.ForEach(folderEntry.FileSystemEntries, _parallelOptions, (entry) =>
+ {
+ if (!_backgroundWorker.CancellationPending)
+ {
+ if (entry is DirectoryEntry)
+ {
+ DoFolderEntryWork(entry as DirectoryEntry);
+ }
+ else if (entry is FileEntry)
+ {
+ DoFileEntryWork(entry as FileEntry);
+ }
+ }
+ });
+
+ if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.Complete) &&
+ folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete))
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.Complete, null);
+ }
+ else if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.EnumerationSuspended || (x as DirectoryEntry).State == FolderEntryState.Complete) &&
+ folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete))
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.NestedAuditingSuspended, null);
+ }
+ }
+
+ if (folderEntry.State == FolderEntryState.NestedAuditingSuspended && !_backgroundWorker.CancellationPending)
+ {
+ Parallel.ForEach(folderEntry.FileSystemEntries, _parallelOptions, (entry) =>
+ {
+ if (!_backgroundWorker.CancellationPending)
+ {
+ if (entry is DirectoryEntry)
+ {
+ DoFolderEntryWork(entry as DirectoryEntry);
+ }
+ }
+ });
+
+ if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.Complete) &&
+ folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete))
+ {
+ _uiContext.Send((_) => folderEntry.State = FolderEntryState.Complete, null);
+ }
+ }
+ }
+
+ private void DoHostWork(Host host)
+ {
+ if (host.State == HostState.New && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => host.State = HostState.LookingUpPtr, null);
+ }
+
+ if (host.State == HostState.LookingUpPtr && !_backgroundWorker.CancellationPending)
+ {
+ if (!_project.Configuration.DisableReverseDnsLookup && !string.IsNullOrEmpty(host.IPAddress))
+ {
+ var ptrRecord = _dnsUtilitiesService.GetPtrRecord(IPAddress.Parse(host.IPAddress));
+ _uiContext.Send((_) => host.PtrRecord = ptrRecord, null);
+ }
+
+ _uiContext.Send((_) => host.State = HostState.CheckingPorts, null);
+ }
+
+ if (host.State == HostState.CheckingPorts && !_backgroundWorker.CancellationPending)
+ {
+ if (_project.Configuration.DisablePortScan)
+ {
+ _uiContext.Send((_) => host.State = HostState.EnumeratingShares, null);
+ }
+ else
+ {
+ var arePortsAccessible = _portScanService.IsTcpPortOpen(host.Name, 445, 1500);
+ if (arePortsAccessible)
+ {
+ _uiContext.Send((_) => host.State = HostState.EnumeratingShares, null);
+ }
+ else
+ {
+ _uiContext.Send((_) => host.State = HostState.Complete, null);
+ }
+ }
+ }
+
+ if ((host.State == HostState.EnumeratingShares || host.State == HostState.AuditingShares || host.State == HostState.NestedAuditingSuspended) && !_backgroundWorker.CancellationPending)
+ {
+ if (_project.Configuration.Credentials.UseCurrentCredentials || _project.Configuration.UseAlternateAuthenticationMethod)
+ {
+ DoAuthenticatedHostWork(host);
+ }
+ else
+ {
+ using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host.Name, _project.Configuration.Credentials.Username, _project.Configuration.Credentials.Domain, _project.Configuration.Credentials.Password))
+ {
+ DoAuthenticatedHostWork(host);
+ }
+ }
+ }
+ }
+
+ private void DoProjectWork()
+ {
+ if (_project.State == ProjectState.Configured && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => _project.State = ProjectState.ExpandingScope, null);
+ }
+
+ if (_project.State == ProjectState.ExpandingScope && !_backgroundWorker.CancellationPending)
+ {
+ _uiContext.Send((_) => _project.Hosts = new ObservableCollection(), null);
+
+ if (_project.Configuration.UseVerbatimScope)
+ {
+ Parallel.ForEach(_project.Configuration.Scope.Split(',').Select(x => x.Trim()), _parallelOptions, (target) =>
+ {
+ if (IPAddress.TryParse(target, out var ipAddress))
+ {
+ _uiContext.Send(
+ (_) =>
+ {
+ _project.Hosts.Add(new Host
+ {
+ IPAddress = ipAddress.ToString()
+ });
+ }, null);
+ }
+ else
+ {
+ _uiContext.Send(
+ (_) =>
+ {
+ _project.Hosts.Add(new Host
+ {
+ PtrRecord = target
+ });
+ }, null);
+ }
+ });
+ }
+ else
+ {
+ Parallel.ForEach(_scopeExpansionService.ExpandScopeToIPAddresses(_project.Configuration.Scope), _parallelOptions, (ipAddress) =>
+ {
+ _uiContext.Send(
+ (_) =>
+ {
+ _project.Hosts.Add(new Host
+ {
+ IPAddress = ipAddress.ToString()
+ });
+ }, null);
+ });
+ }
+
+ _uiContext.Send((_) => _project.State = ProjectState.AuditingHosts, null);
+ }
+
+ if (_project.State == ProjectState.AuditingHosts && !_backgroundWorker.CancellationPending)
+ {
+ Parallel.ForEach(_project.Hosts, _parallelOptions, (host) =>
+ {
+ if (!_backgroundWorker.CancellationPending)
+ {
+ DoHostWork(host);
+ }
+ });
+
+ if (_project.Hosts.All(x => x.State == HostState.Complete))
+ {
+ _uiContext.Send((_) => _project.State = ProjectState.Complete, null);
+ }
+ }
+ }
+
+ private void DoWork(object sender, DoWorkEventArgs e)
+ {
+ Started?.Invoke(this, new EventArgs());
+
+ if (!_project.Configuration.Credentials.UseCurrentCredentials && _project.Configuration.UseAlternateAuthenticationMethod)
+ {
+ var credentials = new UserCredentials(_project.Configuration.Credentials.Domain, _project.Configuration.Credentials.Username, _project.Configuration.Credentials.Password);
+ Impersonation.RunAsUser(credentials, LogonType.NewCredentials, () =>
+ {
+ DoProjectWork();
+ });
+ }
+ else
+ {
+ DoProjectWork();
+ }
+ }
+
+ private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+
+ private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
+ {
+ if (e.Error != null)
+ {
+ throw e.Error;
+ }
+
+ Stopped?.Invoke(this, new EventArgs());
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs b/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs
new file mode 100644
index 0000000..c314567
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs
@@ -0,0 +1,21 @@
+using Dionach.ShareAudit.Model;
+using Dionach.ShareAudit.Modules.Services.Interop;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class ShareInfo1
+ {
+ internal ShareInfo1(SHARE_INFO_1 shi1)
+ {
+ NetName = shi1.shi1_netname;
+ Type = (ShareTypes)shi1.shi1_type;
+ Remark = shi1.shi1_remark;
+ }
+
+ public string NetName { get; }
+
+ public string Remark { get; }
+
+ public ShareTypes Type { get; }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs
new file mode 100644
index 0000000..af69b43
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs
@@ -0,0 +1,73 @@
+using Dionach.ShareAudit.Modules.Services.Interop;
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class SidUtilitiesService : ISidUtilitiesService
+ {
+ private readonly ConcurrentDictionary _sidCache = new ConcurrentDictionary();
+
+ public string SidStringToAccountName(string host, string sid)
+ {
+ const int ERROR_SUCCESS = 0;
+ const int ERROR_INSUFFICIENT_BUFFER = 122;
+
+ if (_sidCache.ContainsKey(sid))
+ {
+ return _sidCache[sid];
+ }
+ else
+ {
+ var name = new StringBuilder();
+ uint cchName = (uint)name.Capacity;
+ var referencedDomainName = new StringBuilder();
+ uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
+
+ byte[] sidByteArray = null;
+ var sid_ptr = new IntPtr(0);
+ NativeMethods.ConvertStringSidToSid(sid, out sid_ptr);
+ var size = (int)NativeMethods.GetLengthSid(sid_ptr);
+ sidByteArray = new byte[size];
+ Marshal.Copy(sid_ptr, sidByteArray, 0, size);
+ Marshal.FreeHGlobal(sid_ptr);
+
+ var result = ERROR_SUCCESS;
+ if (!NativeMethods.LookupAccountSid(host, sidByteArray, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out var sidUse))
+ {
+ result = Marshal.GetLastWin32Error();
+ if (result == ERROR_INSUFFICIENT_BUFFER)
+ {
+ name.EnsureCapacity((int)cchName);
+ referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
+ result = ERROR_SUCCESS;
+ if (!NativeMethods.LookupAccountSid(host, sidByteArray, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse))
+ {
+ result = Marshal.GetLastWin32Error();
+ }
+ }
+ }
+
+ if (result == ERROR_SUCCESS)
+ {
+ if (!string.IsNullOrEmpty(referencedDomainName.ToString()))
+ {
+ _sidCache.TryAdd(sid, referencedDomainName.ToString() + @"\" + name.ToString());
+ }
+ else
+ {
+ _sidCache.TryAdd(sid, name.ToString());
+ }
+ }
+ else
+ {
+ _sidCache.TryAdd(sid, name.ToString());
+ }
+ }
+
+ return _sidCache[sid];
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs
new file mode 100644
index 0000000..d128dee
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs
@@ -0,0 +1,58 @@
+using Dionach.ShareAudit.Modules.Services.Interop;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace Dionach.ShareAudit.Modules.Services
+{
+ public class SmbUtilitiesService : ISmbUtilitiesService
+ {
+ public NetUseConnection CreateNetUseConnection(string host, string username, string domain, string password)
+ {
+ return new NetUseConnection(host, username, domain, password);
+ }
+
+ public IEnumerable NetShareEnum(string host)
+ {
+ const int ERROR_SUCCESS = 0;
+ const int ERROR_MORE_DATA = 234;
+ const int ERROR_NETWORK_PATH_NOT_FOUND = 53;
+ const int ERROR_ACCESS_DENIED = 0x5;
+
+ var maxPreferredLength = 0xFFFFFFFF;
+ var level = 1;
+ var entriesRead = 0;
+ var totalEntries = 0;
+ var resumeHandle = 0;
+ var offset = Marshal.SizeOf(typeof(SHARE_INFO_1));
+ var bufPtr = IntPtr.Zero;
+ var result = ERROR_SUCCESS;
+
+ do
+ {
+ result = NativeMethods.NetShareEnum(host, level, ref bufPtr, maxPreferredLength, ref entriesRead, ref totalEntries, ref resumeHandle);
+
+ if (result == ERROR_SUCCESS || result == ERROR_MORE_DATA || result == ERROR_NETWORK_PATH_NOT_FOUND)
+ {
+ for (int i = 0, lpItem = bufPtr.ToInt32(); i < entriesRead; i++, lpItem += offset)
+ {
+ var pItem = new IntPtr(lpItem);
+
+ yield return new ShareInfo1((SHARE_INFO_1)Marshal.PtrToStructure(pItem, typeof(SHARE_INFO_1)));
+ }
+ }
+ else if (result != ERROR_ACCESS_DENIED)
+ {
+ throw new Win32Exception(result);
+ }
+
+ if (bufPtr != IntPtr.Zero)
+ {
+ NativeMethods.NetApiBufferFree(bufPtr);
+ }
+ }
+ while (result == ERROR_MORE_DATA);
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs
new file mode 100644
index 0000000..81acac5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ public sealed class AndTrueToTrueMultiConverter : IMultiValueConverter
+ {
+ public static AndTrueToTrueMultiConverter Default { get; } = new AndTrueToTrueMultiConverter();
+
+ public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ var andTrue = true;
+ foreach (object value in values)
+ {
+ if (value is bool)
+ {
+ andTrue = andTrue && (bool)value;
+ }
+ }
+
+ return andTrue;
+ }
+
+ object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(AndTrueToTrueMultiConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs
new file mode 100644
index 0000000..caf668e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ public sealed class AndTrueToVisibleMultiConverter : IMultiValueConverter
+ {
+ public static AndTrueToVisibleMultiConverter Default { get; } = new AndTrueToVisibleMultiConverter();
+
+ public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ var visible = true;
+ foreach (object value in values)
+ {
+ if (value is bool)
+ {
+ visible = visible && (bool)value;
+ }
+ }
+
+ return visible ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(AndTrueToVisibleMultiConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs
new file mode 100644
index 0000000..b7cfce0
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Text;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(string), typeof(string))]
+ public sealed class Base64Converter : IValueConverter
+ {
+ public static Base64Converter Default { get; } = new Base64Converter();
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is string)
+ {
+ return Encoding.Default.GetString(System.Convert.FromBase64String(value as string));
+ }
+
+ return string.Empty;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is string)
+ {
+ return System.Convert.ToBase64String(Encoding.Default.GetBytes(value as string));
+ }
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs
new file mode 100644
index 0000000..8e46aae
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(bool), typeof(bool))]
+ public sealed class BoolInversionConverter : IValueConverter
+ {
+ public static BoolInversionConverter Default { get; } = new BoolInversionConverter();
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return !(bool)value;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs
new file mode 100644
index 0000000..c67f186
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(object), typeof(bool?))]
+ public sealed class ComparisonConverter : IValueConverter
+ {
+ public static ComparisonConverter Default { get; } = new ComparisonConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value?.Equals(parameter);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value?.Equals(true) == true ? parameter : Binding.DoNothing;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs
new file mode 100644
index 0000000..6a4d3c8
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(bool), typeof(Visibility))]
+ public sealed class IsFalseToCollapsedConverter : IValueConverter
+ {
+ public static IsFalseToCollapsedConverter Default { get; } = new IsFalseToCollapsedConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value is bool && !(bool)value ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(IsTrueToCollapsedConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs
new file mode 100644
index 0000000..b889faf
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs
@@ -0,0 +1,38 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(object), typeof(bool))]
+ public sealed class IsStateInProgressToBoolConverter : IValueConverter
+ {
+ public static IsStateInProgressToBoolConverter Default { get; } = new IsStateInProgressToBoolConverter();
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is HostState)
+ {
+ return (HostState)value != HostState.Complete && (HostState)value != HostState.NestedAuditingSuspended;
+ }
+
+ if (value is FolderEntryState)
+ {
+ return (FolderEntryState)value != FolderEntryState.Complete && (FolderEntryState)value != FolderEntryState.EnumerationSuspended && (FolderEntryState)value != FolderEntryState.NestedAuditingSuspended;
+ }
+
+ if (value is FileEntryState)
+ {
+ return (FileEntryState)value != FileEntryState.Complete;
+ }
+
+ return Binding.DoNothing;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(IsStateInProgressToBoolConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs
new file mode 100644
index 0000000..b319fef
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(bool), typeof(Visibility))]
+ public sealed class IsTrueToCollapsedConverter : IValueConverter
+ {
+ public static IsTrueToCollapsedConverter Default { get; } = new IsTrueToCollapsedConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value is bool && (bool)value ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(IsTrueToCollapsedConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs
new file mode 100644
index 0000000..8fb5a96
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs
@@ -0,0 +1,61 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.Linq;
+using System.Text;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(FileSystemEntry), typeof(string))]
+ public sealed class ItemDetailToStringConverter : IValueConverter
+ {
+ public static ItemDetailToStringConverter Default { get; } = new ItemDetailToStringConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ if (value is FileSystemEntry)
+ {
+ if ((value is FileEntry && (value as FileEntry).State < FileEntryState.Complete) ||
+ (value is IFolderEntry && (value as IFolderEntry).State < FolderEntryState.GettingEffectiveAccess))
+ {
+ return string.Empty;
+ }
+
+ var fileSystemEntry = value as FileSystemEntry;
+ var sb = new StringBuilder();
+ sb.AppendLine(fileSystemEntry.FullName);
+ sb.AppendLine();
+ sb.AppendLine($"Effective Access: {(fileSystemEntry.EffectiveAccess.Write ? "Write" : "Read")}");
+ sb.AppendLine();
+
+ var maxIdentity = Math.Max("Identity".Length, fileSystemEntry.AccessRules.Max(x => x.Identity.Length));
+ var maxRights = Math.Max("Rights".Length, fileSystemEntry.AccessRules.Max(x => x.Rights.Length));
+ var maxType = Math.Max("Type".Length, fileSystemEntry.AccessRules.Max(x => x.Type.ToString().Length));
+ var maxInherited = Math.Max("Inherited".Length, fileSystemEntry.AccessRules.Max(x => x.Inherited.ToString().Length));
+ sb.AppendLine($"| {"Identity".PadRight(maxIdentity)} | {"Rights".PadRight(maxRights)} | {"Type".PadRight(maxType)} | {"Inherited".PadRight(maxInherited)} |");
+ sb.AppendLine($"| {string.Empty.PadRight(maxIdentity, '-')} | {string.Empty.PadRight(maxRights, '-')} | {string.Empty.PadRight(maxType, '-')} | {string.Empty.PadRight(maxInherited, '-')} |");
+ foreach (var accessRule in fileSystemEntry.AccessRules)
+ {
+ sb.AppendLine($"| {accessRule.Identity.PadRight(maxIdentity)} | {accessRule.Rights.PadRight(maxRights)} | {accessRule.Type.ToString().PadRight(maxType)} | {accessRule.Inherited.ToString().PadRight(maxInherited)} |");
+ }
+
+ if (value is FileEntry && fileSystemEntry.EffectiveAccess.Read)
+ {
+ sb.AppendLine();
+ sb.AppendLine("Preview:");
+ sb.AppendLine("--------");
+ sb.Append(Encoding.Default.GetString(System.Convert.FromBase64String((value as FileEntry).Head)));
+ }
+
+ return sb.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(ItemDetailToStringConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs
new file mode 100644
index 0000000..5b3bf5f
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ public sealed class OrTrueToVisibleMultiConverter : IMultiValueConverter
+ {
+ public static OrTrueToVisibleMultiConverter Default { get; } = new OrTrueToVisibleMultiConverter();
+
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ var visible = false;
+ foreach (object value in values)
+ {
+ if (value is bool)
+ {
+ visible = visible || (bool)value;
+ }
+ }
+
+ return visible ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(OrTrueToVisibleMultiConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs
new file mode 100644
index 0000000..0d3a132
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs
@@ -0,0 +1,41 @@
+using Dionach.ShareAudit.Model;
+using System;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(ShareTypes), typeof(ImageSource))]
+ public sealed class ShareTypeToIconSourceConverter : IValueConverter
+ {
+ public static ShareTypeToIconSourceConverter Default { get; } = new ShareTypeToIconSourceConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ if (!(value is ShareTypes))
+ {
+ throw new NotSupportedException($"{nameof(ShareTypeToIconSourceConverter)} can only be used with {nameof(ShareTypes)} values");
+ }
+
+ switch ((ShareTypes)value)
+ {
+ case ShareTypes.Disktree:
+ return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_4.ico";
+ case ShareTypes.Device:
+ return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_32.ico";
+ case ShareTypes.PrintQueue:
+ return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_51.ico";
+ case ShareTypes.Special:
+ return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_78.ico";
+
+ default:
+ return Binding.DoNothing;
+ }
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(ShareTypeToIconSourceConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs
new file mode 100644
index 0000000..79012f5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(string), typeof(Visibility))]
+ public sealed class StringIsNullOrEmptyToCollapsedConverter : IValueConverter
+ {
+ public static StringIsNullOrEmptyToCollapsedConverter Default { get; } = new StringIsNullOrEmptyToCollapsedConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return string.IsNullOrEmpty(value as string) ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(StringIsNullOrEmptyToCollapsedConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs
new file mode 100644
index 0000000..9b36b7a
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Windows.Data;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Converters
+{
+ [ValueConversion(typeof(string), typeof(bool))]
+ public sealed class StringIsNullOrEmptyToTrueConverter : IValueConverter
+ {
+ public static StringIsNullOrEmptyToTrueConverter Default { get; } = new StringIsNullOrEmptyToTrueConverter();
+
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return string.IsNullOrEmpty(value as string);
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotSupportedException($"{nameof(StringIsNullOrEmptyToTrueConverter)} can only be used in OneWay bindings");
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj b/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj
new file mode 100644
index 0000000..4ee989b
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj
@@ -0,0 +1,170 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {28CF3837-FF58-463B-AF81-E6B0039DE55F}
+ library
+ Properties
+ Dionach.ShareAudit.Modules.UserInterface
+ Dionach.ShareAudit.Modules.UserInterface
+ v4.6.1
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ PackageReference
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+ AuditView.xaml
+
+
+ ConfigurationView.xaml
+
+
+ HelpView.xaml
+
+
+ ImportScopeView.xaml
+
+
+ WelcomeView.xaml
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ 4.7.0.9
+
+
+ 7.1.0.431
+
+
+
+
+
+ {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}
+ Dionach.ShareAudit.Model
+
+
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}
+ Dionach.ShareAudit.Modules.Services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs
new file mode 100644
index 0000000..bbd482c
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs
@@ -0,0 +1,21 @@
+using System.Windows;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Helpers
+{
+ public class BindingProxy : Freezable
+ {
+ public static readonly DependencyProperty DataProperty =
+ DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
+
+ public object Data
+ {
+ get => (object)GetValue(DataProperty);
+ set => SetValue(DataProperty, value);
+ }
+
+ protected override Freezable CreateInstanceCore()
+ {
+ return new BindingProxy();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs
new file mode 100644
index 0000000..19bc50a
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs
@@ -0,0 +1,68 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Helpers
+{
+ public static class PasswordHelper
+ {
+ public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, OnAttachChanged));
+
+ public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper), new FrameworkPropertyMetadata(string.Empty, OnPasswordChanged));
+
+ private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(PasswordHelper));
+
+ [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
+ public static bool GetAttach(DependencyObject dp) => (bool)dp.GetValue(AttachProperty);
+
+ [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
+ public static string GetPassword(DependencyObject dp) => (string)dp.GetValue(PasswordProperty);
+
+ public static void SetAttach(DependencyObject dp, bool value) => dp.SetValue(AttachProperty, value);
+
+ public static void SetPassword(DependencyObject dp, string value) => dp.SetValue(PasswordProperty, value);
+
+ [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
+ private static bool GetIsUpdating(DependencyObject dp) => (bool)dp.GetValue(IsUpdatingProperty);
+
+ private static void OnAttachChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ if (!(sender is PasswordBox passwordBox))
+ {
+ return;
+ }
+
+ if ((bool)e.OldValue)
+ {
+ passwordBox.PasswordChanged -= PasswordChanged;
+ }
+
+ if ((bool)e.NewValue)
+ {
+ passwordBox.PasswordChanged += PasswordChanged;
+ }
+ }
+
+ private static void OnPasswordChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ PasswordBox passwordBox = sender as PasswordBox;
+ passwordBox.PasswordChanged -= PasswordChanged;
+
+ if (!(bool)GetIsUpdating(passwordBox))
+ {
+ passwordBox.Password = (string)e.NewValue;
+ }
+
+ passwordBox.PasswordChanged += PasswordChanged;
+ }
+
+ private static void PasswordChanged(object sender, RoutedEventArgs e)
+ {
+ PasswordBox passwordBox = sender as PasswordBox;
+ SetIsUpdating(passwordBox, true);
+ SetPassword(passwordBox, passwordBox.Password);
+ SetIsUpdating(passwordBox, false);
+ }
+
+ private static void SetIsUpdating(DependencyObject dp, bool value) => dp.SetValue(IsUpdatingProperty, value);
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs
new file mode 100644
index 0000000..b4f763c
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs
@@ -0,0 +1,65 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Helpers
+{
+ public static class TreeViewExtension
+ {
+ public static readonly DependencyProperty SelectItemOnRightClickProperty = DependencyProperty.RegisterAttached(
+ "SelectItemOnRightClick",
+ typeof(bool),
+ typeof(TreeViewExtension),
+ new UIPropertyMetadata(false, OnSelectItemOnRightClickChanged));
+
+ [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
+ public static bool GetSelectItemOnRightClick(DependencyObject d)
+ {
+ return (bool)d.GetValue(SelectItemOnRightClickProperty);
+ }
+
+ public static void SetSelectItemOnRightClick(DependencyObject d, bool value)
+ {
+ d.SetValue(SelectItemOnRightClickProperty, value);
+ }
+
+ public static TreeViewItem VisualUpwardSearch(DependencyObject source)
+ {
+ while (source != null && !(source is TreeViewItem))
+ {
+ source = VisualTreeHelper.GetParent(source);
+ }
+
+ return source as TreeViewItem;
+ }
+
+ private static void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
+
+ if (treeViewItem != null)
+ {
+ treeViewItem.Focus();
+ e.Handled = true;
+ }
+ }
+
+ private static void OnSelectItemOnRightClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ bool selectItemOnRightClick = (bool)e.NewValue;
+
+ if (d is TreeView treeView)
+ {
+ if (selectItemOnRightClick)
+ {
+ treeView.PreviewMouseRightButtonDown += OnPreviewMouseRightButtonDown;
+ }
+ else
+ {
+ treeView.PreviewMouseRightButtonDown -= OnPreviewMouseRightButtonDown;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs
new file mode 100644
index 0000000..c4e4308
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Helpers
+{
+ public class TreeViewHelper
+ {
+ // Using a DependencyProperty as the backing store for SelectedItem. This enables animation,
+ // styling, binding, etc...
+ public static readonly DependencyProperty SelectedItemProperty =
+ DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, OnSelectedItemChanged));
+
+ private static Dictionary behaviors = new Dictionary();
+
+ [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
+ public static object GetSelectedItem(DependencyObject obj)
+ {
+ return (object)obj.GetValue(SelectedItemProperty);
+ }
+
+ public static void SetSelectedItem(DependencyObject obj, object value)
+ {
+ obj.SetValue(SelectedItemProperty, value);
+ }
+
+ private static void OnSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
+ {
+ if (!(obj is TreeView))
+ {
+ return;
+ }
+
+ if (!behaviors.ContainsKey(obj))
+ {
+ behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
+ }
+
+ TreeViewSelectedItemBehavior view = behaviors[obj];
+ view.ChangeSelectedItem(e.NewValue);
+ }
+
+ private class TreeViewSelectedItemBehavior
+ {
+ private TreeView _view;
+
+ public TreeViewSelectedItemBehavior(TreeView view)
+ {
+ _view = view;
+ view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
+ }
+
+ internal void ChangeSelectedItem(object p)
+ {
+ if (_view.ItemContainerGenerator.ContainerFromItem(p) is TreeViewItem item)
+ {
+ item.IsSelected = true;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png b/src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png
new file mode 100644
index 0000000..1ae2155
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/Icon.png b/src/Dionach.ShareAudit.Modules.UserInterface/Images/Icon.png
new file mode 100644
index 0000000..124aee5
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/Icon.png differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_2.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_2.ico
new file mode 100644
index 0000000..cb4e64d
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_2.ico differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_32.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_32.ico
new file mode 100644
index 0000000..ef1ba98
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_32.ico differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico
new file mode 100644
index 0000000..bdc381b
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico
new file mode 100644
index 0000000..5a25434
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico
new file mode 100644
index 0000000..437d65d
Binary files /dev/null and b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico differ
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8706551
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following set of attributes.
+// Change these attribute values to modify the information associated with an assembly.
+[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.UserInterface")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.UserInterface")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you
+// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// In order to begin building localizable applications, set
+// CultureYouAreCodingWith in your .csproj file inside a . For
+// example, if you are using US english in your source files, set the to en-US. Then
+// uncomment the NeutralResourceLanguage attribute below. Update the "en-US" in the line below to
+// match the UICulture setting in the project file.
+[assembly: NeutralResourcesLanguage("en-GB"/*, UltimateResourceFallbackLocation.Satellite*/)]
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
+]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version Minor Version Build Number Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers by using the '*'
+// as shown below: [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: CLSCompliant(false)]
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..d623def
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Properties
+{
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if (object.ReferenceEquals(resourceMan, null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dionach.ShareAudit.Modules.UserInterface.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..8202770
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs b/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs
new file mode 100644
index 0000000..5b6bafe
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs
@@ -0,0 +1,27 @@
+using Dionach.ShareAudit.Modules.UserInterface.Views;
+using Prism.Ioc;
+using Prism.Modularity;
+using Prism.Regions;
+
+namespace Dionach.ShareAudit.Modules.UserInterface
+{
+ public class UserInterfaceModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ var regionManager = containerProvider.Resolve();
+ var region = regionManager.Regions["ContentRegion"];
+
+ var welcomeView = containerProvider.Resolve();
+ region.Add(welcomeView);
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterForNavigation();
+ containerRegistry.RegisterForNavigation();
+ containerRegistry.RegisterForNavigation();
+ containerRegistry.RegisterForNavigation();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs
new file mode 100644
index 0000000..ce86cd9
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs
@@ -0,0 +1,231 @@
+using Dionach.ShareAudit.Model;
+using Dionach.ShareAudit.Modules.Services;
+using Microsoft.Win32;
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using System;
+using System.Diagnostics;
+using System.Windows;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels
+{
+ public class AuditViewModel : BindableBase, INavigationAware
+ {
+ private readonly IFileSystemStoreService _fileSystemStoreService;
+ private readonly IShareAuditService _shareAuditService;
+ private readonly ISmbUtilitiesService _smbUtilitiesService;
+ private bool _isBusy;
+ private bool _isRunning;
+ private Project _project;
+ private string _projectPath;
+ private bool _runningInitialAutomaticAudit = false;
+ private object _selectedItem = new object();
+
+ public AuditViewModel(
+ IFileSystemStoreService fileSystemStoreService,
+ IShareAuditService shareAuditService,
+ ISmbUtilitiesService smbUtilitiesService)
+ {
+ _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService));
+ _shareAuditService = shareAuditService ?? throw new ArgumentNullException(nameof(shareAuditService));
+ _smbUtilitiesService = smbUtilitiesService ?? throw new ArgumentNullException(nameof(smbUtilitiesService));
+
+ _shareAuditService.Started += (sender, e) =>
+ {
+ IsRunning = true;
+ IsBusy = false;
+ };
+ _shareAuditService.Stopped += async (sender, e) =>
+ {
+ IsRunning = false;
+ await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath);
+
+ if (_runningInitialAutomaticAudit)
+ {
+ MessageBox.Show("The initial audit is now complete, you may proceed to review the results", "Initial Audit Complete", MessageBoxButton.OK, MessageBoxImage.Information);
+ _runningInitialAutomaticAudit = false;
+ }
+
+ IsBusy = false;
+ };
+
+ Export = new DelegateCommand(OnExport, CanExport).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning);
+ StartAudit = new DelegateCommand(OnStartAudit, CanStartAudit).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning);
+ StopAudit = new DelegateCommand(OnStopAudit, CanStopAudit).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning);
+ AuditFolder = new DelegateCommand(OnAuditFolder, CanAuditFolder).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectedItem);
+ RevealInExplorer = new DelegateCommand(OnRevealInExplorer, CanRevealInExplorer).ObservesProperty(() => IsBusy).ObservesProperty(() => SelectedItem);
+ }
+
+ public DelegateCommand AuditFolder { get; }
+
+ public DelegateCommand Export { get; }
+
+ public bool IsBusy
+ {
+ get => _isBusy;
+ private set => SetProperty(ref _isBusy, value);
+ }
+
+ public bool IsRunning
+ {
+ get => _isRunning;
+ private set => SetProperty(ref _isRunning, value);
+ }
+
+ public Project Project
+ {
+ get => _project;
+ private set => SetProperty(ref _project, value);
+ }
+
+ public string ProjectPath
+ {
+ get => _projectPath;
+ private set => SetProperty(ref _projectPath, value);
+ }
+
+ public DelegateCommand RevealInExplorer { get; }
+
+ public object SelectedItem
+ {
+ get => _selectedItem;
+ set => SetProperty(ref _selectedItem, value);
+ }
+
+ public DelegateCommand StartAudit { get; }
+
+ public DelegateCommand StopAudit { get; }
+
+ public bool IsNavigationTarget(NavigationContext navigationContext) => false;
+
+ public void OnNavigatedFrom(NavigationContext navigationContext)
+ {
+ }
+
+ public void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ IsBusy = true;
+
+ ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath));
+ Project = navigationContext.Parameters.GetValue(nameof(Project));
+
+ if (Project.State == ProjectState.Configured && !Project.Configuration.IsReadOnly)
+ {
+ _runningInitialAutomaticAudit = true;
+ OnStartAudit();
+ }
+
+ IsBusy = false;
+ }
+
+ private bool CanAuditFolder()
+ {
+ return SelectedItem is IFolderEntry &&
+ (SelectedItem as IFolderEntry).State == FolderEntryState.EnumerationSuspended &&
+ !IsBusy &&
+ !IsRunning &&
+ !(Project?.Configuration?.IsReadOnly ?? true);
+ }
+
+ private bool CanExport() => !IsBusy && !IsRunning;
+
+ private bool CanRevealInExplorer()
+ {
+ return (SelectedItem is IFolderEntry || SelectedItem is FileEntry) &&
+ !IsBusy &&
+ !(Project?.Configuration?.IsReadOnly ?? true);
+ }
+
+ private bool CanStartAudit() => !IsBusy && !IsRunning && !(Project?.Configuration?.IsReadOnly ?? true);
+
+ private bool CanStopAudit() => !IsBusy && IsRunning;
+
+ private void OnAuditFolder()
+ {
+ IsBusy = true;
+
+ (SelectedItem as IFolderEntry).State = FolderEntryState.EnumeratingFilesystemEntries;
+
+ _shareAuditService.StartAudit(Project);
+ }
+
+ private async void OnExport()
+ {
+ IsBusy = true;
+
+ var dialog = new SaveFileDialog
+ {
+ Filter = _fileSystemStoreService.ExportFilter,
+ FileName = _fileSystemStoreService.ExportDefaultFilename
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ await _fileSystemStoreService.ExportProjectAsync(_project, dialog.FileName);
+ }
+
+ IsBusy = false;
+ }
+
+ private void OnRevealInExplorer()
+ {
+ IsBusy = true;
+
+ if (SelectedItem is FileEntry)
+ {
+ var host = (SelectedItem as FileEntry).FullName.Trim('\\').Split('\\')[0];
+ if (_project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ using (Process.Start("explorer.exe", $"/select,\"{(SelectedItem as FileEntry).FullName}\""))
+ {
+ }
+ }
+ else
+ {
+ using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Domain, Project.Configuration.Credentials.Password))
+ {
+ using (Process.Start("explorer.exe", $"/select,\"{(SelectedItem as FileEntry).FullName}\""))
+ {
+ }
+ }
+ }
+ }
+ else
+ {
+ var host = (SelectedItem as IFolderEntry).FullName.Trim('\\').Split('\\')[0];
+ if (_project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ using (Process.Start("explorer.exe", $"\"{(SelectedItem as IFolderEntry).FullName}\""))
+ {
+ }
+ }
+ else
+ {
+ using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Domain, Project.Configuration.Credentials.Password))
+ {
+ using (Process.Start("explorer.exe", $"\"{(SelectedItem as IFolderEntry).FullName}\""))
+ {
+ }
+ }
+ }
+ }
+
+ IsBusy = false;
+ }
+
+ private void OnStartAudit()
+ {
+ IsBusy = true;
+
+ _shareAuditService.StartAudit(Project);
+ }
+
+ private void OnStopAudit()
+ {
+ IsBusy = true;
+
+ _shareAuditService.StopAudit();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs
new file mode 100644
index 0000000..0d90856
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs
@@ -0,0 +1,283 @@
+using Dionach.ShareAudit.Model;
+using Dionach.ShareAudit.Modules.Services;
+using Dionach.ShareAudit.Modules.UserInterface.Views;
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using System;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels
+{
+ public class ConfigurationViewModel : BindableBase, INavigationAware
+ {
+ private readonly ICredentialsValidationService _credentialsValidationService;
+ private readonly IFileSystemStoreService _fileSystemStoreService;
+ private readonly IRegionManager _regionManager;
+ private readonly IScopeNormalizationService _scopeNormalizationService;
+ private readonly IScopeValidationService _scopeValidationService;
+ private string _errorMessageForCredentials;
+ private string _errorMessageForDomain;
+ private string _errorMessageForPassword;
+ private string _errorMessageForScope;
+ private string _errorMessageForUsername;
+ private bool _isBusy;
+ private bool _isProjectUnconfigured;
+ private Project _project;
+ private string _projectPath;
+
+ public ConfigurationViewModel(
+ IRegionManager regionManager,
+ IFileSystemStoreService fileSystemStoreService,
+ ICredentialsValidationService credentialsValidationService,
+ IScopeValidationService scopeValidationService,
+ IScopeNormalizationService scopeNormalizationService)
+ {
+ _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
+ _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService));
+ _credentialsValidationService = credentialsValidationService ?? throw new ArgumentNullException(nameof(credentialsValidationService));
+ _scopeValidationService = scopeValidationService ?? throw new ArgumentNullException(nameof(scopeValidationService));
+ _scopeNormalizationService = scopeNormalizationService ?? throw new ArgumentNullException(nameof(scopeNormalizationService));
+
+ Cancel = new DelegateCommand(OnCancel, CanCancel).ObservesProperty(() => IsBusy);
+ Next = new DelegateCommand(OnNext, CanNext).ObservesProperty(() => IsBusy);
+ Import = new DelegateCommand(OnImport, CanImport).ObservesProperty(() => IsBusy).ObservesProperty(() => Project.Configuration.Scope);
+ UseCurrentCredentials = new DelegateCommand(OnUseCurrentCredentials, CanUseCurrentCredentials).ObservesProperty(() => IsBusy);
+ }
+
+ public DelegateCommand Cancel { get; }
+
+ public string ErrorMessageForCredentials
+ {
+ get => _errorMessageForCredentials;
+ set => SetProperty(ref _errorMessageForCredentials, value);
+ }
+
+ public string ErrorMessageForDomain
+ {
+ get => _errorMessageForDomain;
+ set => SetProperty(ref _errorMessageForDomain, value);
+ }
+
+ public string ErrorMessageForPassword
+ {
+ get => _errorMessageForPassword;
+ set => SetProperty(ref _errorMessageForPassword, value);
+ }
+
+ public string ErrorMessageForScope
+ {
+ get => _errorMessageForScope;
+ set => SetProperty(ref _errorMessageForScope, value);
+ }
+
+ public string ErrorMessageForUsername
+ {
+ get => _errorMessageForUsername;
+ set => SetProperty(ref _errorMessageForUsername, value);
+ }
+
+ public DelegateCommand Import { get; }
+
+ public bool IsBusy
+ {
+ get => _isBusy;
+ private set => SetProperty(ref _isBusy, value);
+ }
+
+ public bool IsProjectUnconfigured
+ {
+ get => _isProjectUnconfigured;
+ private set => SetProperty(ref _isProjectUnconfigured, value);
+ }
+
+ public DelegateCommand Next { get; }
+
+ public Project Project
+ {
+ get => _project;
+ private set => SetProperty(ref _project, value);
+ }
+
+ public string ProjectPath
+ {
+ get => _projectPath;
+ private set => SetProperty(ref _projectPath, value);
+ }
+
+ public DelegateCommand UseCurrentCredentials { get; }
+
+ public bool IsNavigationTarget(NavigationContext navigationContext) => false;
+
+ public void OnNavigatedFrom(NavigationContext navigationContext)
+ {
+ }
+
+ public async void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath));
+ if (navigationContext.Parameters.ContainsKey(nameof(Project)))
+ {
+ Project = navigationContext.Parameters.GetValue(nameof(Project));
+ }
+ else
+ {
+ Project = await _fileSystemStoreService.LoadProjectAsync(ProjectPath);
+ }
+
+ IsProjectUnconfigured = Project.State == ProjectState.New;
+ }
+
+ private bool CanCancel()
+ {
+ return !IsBusy;
+ }
+
+ private bool CanImport()
+ {
+ return !IsBusy && string.IsNullOrEmpty(Project?.Configuration?.Scope);
+ }
+
+ private bool CanNext()
+ {
+ return !IsBusy;
+ }
+
+ private bool CanUseCurrentCredentials() => !IsBusy;
+
+ private void OnCancel() => _regionManager.RequestNavigate("ContentRegion", nameof(WelcomeView));
+
+ private async void OnImport()
+ {
+ IsBusy = true;
+
+ ErrorMessageForDomain = string.Empty;
+ ErrorMessageForUsername = string.Empty;
+ ErrorMessageForPassword = string.Empty;
+ ErrorMessageForCredentials = string.Empty;
+
+#pragma warning disable IDE0042 // Deconstruct variable declaration
+ var domainValidationResult = await _credentialsValidationService.ValidateDomainAsync(Project.Configuration.Credentials.Domain);
+ ErrorMessageForDomain = domainValidationResult.errorMessage;
+
+ var usernameValidationResult = await _credentialsValidationService.ValidateUsernameAsync(Project.Configuration.Credentials.Username);
+ ErrorMessageForUsername = usernameValidationResult.errorMessage;
+
+ var passwordValidationResult = (isValid: true, errorMessage: string.Empty);
+ if (!Project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ passwordValidationResult = await _credentialsValidationService.ValidatePasswordAsync(Project.Configuration.Credentials.Password);
+ ErrorMessageForPassword = passwordValidationResult.errorMessage;
+ }
+
+ var credentialsValidationResult = (isValid: true, errorMessage: string.Empty);
+#pragma warning restore IDE0042 // Deconstruct variable declaration
+ if (!Project.Configuration.Credentials.UseCurrentCredentials && domainValidationResult.isValid && usernameValidationResult.isValid && passwordValidationResult.isValid)
+ {
+ credentialsValidationResult = await _credentialsValidationService.ValidateCredentialsAsync(Project.Configuration.Credentials);
+ ErrorMessageForCredentials = credentialsValidationResult.errorMessage;
+ }
+
+ if (credentialsValidationResult.isValid)
+ {
+ await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath);
+
+ var parameters = new NavigationParameters
+ {
+ { nameof(ImportScopeViewModel.ProjectPath), ProjectPath },
+ { nameof(ImportScopeViewModel.Project), Project }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(ImportScopeView), parameters);
+ }
+
+ IsBusy = false;
+ }
+
+ private async void OnNext()
+ {
+ IsBusy = true;
+
+ ErrorMessageForDomain = string.Empty;
+ ErrorMessageForUsername = string.Empty;
+ ErrorMessageForPassword = string.Empty;
+ ErrorMessageForCredentials = string.Empty;
+ ErrorMessageForScope = string.Empty;
+
+ var credentialsValidationResult = (isValid: true, errorMessage: string.Empty);
+ if (!Project.Configuration.IsReadOnly && !Project.Configuration.Credentials.UseCurrentCredentials)
+ {
+#pragma warning disable IDE0042 // Deconstruct variable declaration
+ var domainValidationResult = await _credentialsValidationService.ValidateDomainAsync(Project.Configuration.Credentials.Domain);
+ ErrorMessageForDomain = domainValidationResult.errorMessage;
+
+ var usernameValidationResult = await _credentialsValidationService.ValidateUsernameAsync(Project.Configuration.Credentials.Username);
+ ErrorMessageForUsername = usernameValidationResult.errorMessage;
+
+ var passwordValidationResult = (isValid: true, errorMessage: string.Empty);
+ if (!Project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ passwordValidationResult = await _credentialsValidationService.ValidatePasswordAsync(Project.Configuration.Credentials.Password);
+ ErrorMessageForPassword = passwordValidationResult.errorMessage;
+ }
+
+ if (domainValidationResult.isValid && usernameValidationResult.isValid && passwordValidationResult.isValid)
+ {
+ credentialsValidationResult = await _credentialsValidationService.ValidateCredentialsAsync(Project.Configuration.Credentials);
+ ErrorMessageForCredentials = credentialsValidationResult.errorMessage;
+ }
+ }
+
+ var scopeValidationResult = await _scopeValidationService.ValidateScopeAsync(Project.Configuration.Scope);
+#pragma warning restore IDE0042 // Deconstruct variable declaration
+ ErrorMessageForScope = scopeValidationResult.errorMessage;
+
+ if (scopeValidationResult.isValid)
+ {
+ Project.Configuration.Scope = await _scopeNormalizationService.NormalizeScopeAsync(Project.Configuration.Scope);
+ }
+
+ if ((!Project.Configuration.IsReadOnly && credentialsValidationResult.isValid && scopeValidationResult.isValid) || (Project.Configuration.IsReadOnly && scopeValidationResult.isValid))
+ {
+ if (Project.State < ProjectState.Configured)
+ {
+ Project.State = ProjectState.Configured;
+ }
+
+ await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath);
+
+ var parameters = new NavigationParameters
+ {
+ { nameof(ImportScopeViewModel.ProjectPath), ProjectPath },
+ { nameof(ImportScopeViewModel.Project), Project }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(AuditView), parameters);
+ }
+
+ IsBusy = false;
+ }
+
+ private async void OnUseCurrentCredentials()
+ {
+ IsBusy = true;
+
+ if (Project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ (var domain, var username) = await _credentialsValidationService.GetCurrentUserInformation();
+ Project.Configuration.Credentials.Domain = domain;
+ Project.Configuration.Credentials.Username = username;
+ Project.Configuration.Credentials.Password = string.Empty;
+ Project.Configuration.UseAlternateAuthenticationMethod = false;
+ }
+ else
+ {
+ Project.Configuration.Credentials.Domain = string.Empty;
+ Project.Configuration.Credentials.Username = string.Empty;
+ Project.Configuration.Credentials.Password = string.Empty;
+ Project.Configuration.UseAlternateAuthenticationMethod = true;
+ }
+
+ IsBusy = false;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs
new file mode 100644
index 0000000..04ab0bc
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs
@@ -0,0 +1,24 @@
+using Dionach.ShareAudit.Modules.UserInterface.Views;
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using System;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels
+{
+ public class HelpViewModel : BindableBase
+ {
+ private readonly IRegionManager _regionManager;
+
+ public HelpViewModel(IRegionManager regionManager)
+ {
+ _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
+
+ Back = new DelegateCommand(OnBack, () => true);
+ }
+
+ public DelegateCommand Back { get; }
+
+ private void OnBack() => _regionManager.RequestNavigate("ContentRegion", nameof(WelcomeView));
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs
new file mode 100644
index 0000000..75ee97e
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs
@@ -0,0 +1,123 @@
+using Dionach.ShareAudit.Model;
+using Dionach.ShareAudit.Modules.Services;
+using Dionach.ShareAudit.Modules.UserInterface.Views;
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using System;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels
+{
+ public class ImportScopeViewModel : BindableBase, INavigationAware
+ {
+ private readonly IFileSystemStoreService _fileSystemStoreService;
+ private readonly IImportScopeFromActiveDirectoryService _importScopeFromActiveDirectoryService;
+ private readonly IRegionManager _regionManager;
+ private string _domain;
+ private ImportComputerType _importComputerType = ImportComputerType.Servers;
+ private bool _isBusy;
+ private Project _project;
+ private string _projectPath;
+
+ public ImportScopeViewModel(
+ IRegionManager regionManager,
+ IFileSystemStoreService fileSystemStoreService,
+ IImportScopeFromActiveDirectoryService importScopeFromActiveDirectoryService)
+ {
+ _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
+ _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService));
+ _importScopeFromActiveDirectoryService = importScopeFromActiveDirectoryService ?? throw new ArgumentNullException(nameof(importScopeFromActiveDirectoryService));
+
+ Cancel = new DelegateCommand(OnCancel, CanCancel).ObservesProperty(() => IsBusy);
+ Import = new DelegateCommand(OnImport, CanImport).ObservesProperty(() => IsBusy);
+ }
+
+ public DelegateCommand Cancel { get; }
+
+ public string Domain
+ {
+ get => _domain;
+ set => SetProperty(ref _domain, value);
+ }
+
+ public DelegateCommand Import { get; }
+
+ public ImportComputerType ImportComputerType
+ {
+ get => _importComputerType;
+ set => SetProperty(ref _importComputerType, value);
+ }
+
+ public bool IsBusy
+ {
+ get => _isBusy;
+ private set => SetProperty(ref _isBusy, value);
+ }
+
+ public Project Project
+ {
+ get => _project;
+ private set => SetProperty(ref _project, value);
+ }
+
+ public string ProjectPath
+ {
+ get => _projectPath;
+ private set => SetProperty(ref _projectPath, value);
+ }
+
+ public bool IsNavigationTarget(NavigationContext navigationContext) => false;
+
+ public void OnNavigatedFrom(NavigationContext navigationContext)
+ {
+ }
+
+ public void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath));
+ Project = navigationContext.Parameters.GetValue(nameof(Project));
+ Domain = Project.Configuration.Credentials.Domain;
+ }
+
+ private bool CanCancel() => !IsBusy;
+
+ private bool CanImport() => !IsBusy;
+
+ private void OnCancel()
+ {
+ var parameters = new NavigationParameters
+ {
+ { nameof(ConfigurationViewModel.ProjectPath), ProjectPath },
+ { nameof(ConfigurationViewModel.Project), Project }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters);
+ }
+
+ private async void OnImport()
+ {
+ IsBusy = true;
+
+ if (Project.Configuration.Credentials.UseCurrentCredentials)
+ {
+ Project.Configuration.Scope = await _importScopeFromActiveDirectoryService.Import(Domain, ImportComputerType);
+ }
+ else
+ {
+ Project.Configuration.Scope = await _importScopeFromActiveDirectoryService.Import(Domain, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Password, ImportComputerType);
+ }
+
+ await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath);
+
+ var parameters = new NavigationParameters
+ {
+ { nameof(ConfigurationViewModel.ProjectPath), ProjectPath },
+ { nameof(ConfigurationViewModel.Project), Project }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters);
+
+ IsBusy = false;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs
new file mode 100644
index 0000000..e20bc7a
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs
@@ -0,0 +1,97 @@
+using Dionach.ShareAudit.Modules.Services;
+using Dionach.ShareAudit.Modules.UserInterface.Views;
+using Microsoft.Win32;
+using Prism.Commands;
+using Prism.Mvvm;
+using Prism.Regions;
+using System;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels
+{
+ public class WelcomeViewModel : BindableBase
+ {
+ private readonly IFileSystemStoreService _fileSystemStoreService;
+ private readonly IRegionManager _regionManager;
+ private bool _isBusy;
+
+ public WelcomeViewModel(IRegionManager regionManager, IFileSystemStoreService fileSystemStoreService)
+ {
+ _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
+ _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService));
+
+ Banner = "/Dionach.ShareAudit.Modules.UserInterface;component/Images/Banner.png";
+ New = new DelegateCommand(OnNew, CanNew).ObservesProperty(() => IsBusy);
+ Load = new DelegateCommand(OnLoad, CanLoad).ObservesProperty(() => IsBusy);
+ Help = new DelegateCommand(OnHelp, CanHelp).ObservesProperty(() => IsBusy);
+ }
+
+ public string Banner { get; }
+
+ public DelegateCommand Help { get; }
+
+ public bool IsBusy
+ {
+ get => _isBusy;
+ set => SetProperty(ref _isBusy, value);
+ }
+
+ public DelegateCommand Load { get; }
+
+ public DelegateCommand New { get; }
+
+ private bool CanHelp() => !IsBusy;
+
+ private bool CanLoad() => !IsBusy;
+
+ private bool CanNew() => !IsBusy;
+
+ private void OnHelp() => _regionManager.RequestNavigate("ContentRegion", nameof(HelpView));
+
+ private void OnLoad()
+ {
+ IsBusy = true;
+
+ var dialog = new OpenFileDialog
+ {
+ Filter = _fileSystemStoreService.ShareAuditFilter,
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ var parameters = new NavigationParameters
+ {
+ { nameof(ConfigurationViewModel.ProjectPath), dialog.FileName }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters);
+ }
+
+ IsBusy = false;
+ }
+
+ private async void OnNew()
+ {
+ IsBusy = true;
+
+ var dialog = new SaveFileDialog
+ {
+ Filter = _fileSystemStoreService.ShareAuditFilter,
+ FileName = _fileSystemStoreService.ShareAuditDefaultFilename
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ await _fileSystemStoreService.CreateProjectAsync(dialog.FileName);
+
+ var parameters = new NavigationParameters
+ {
+ { nameof(ConfigurationViewModel.ProjectPath), dialog.FileName }
+ };
+
+ _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters);
+ }
+
+ IsBusy = false;
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml
new file mode 100644
index 0000000..93ea707
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml.cs
new file mode 100644
index 0000000..55c8af4
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Views
+{
+ ///
+ /// Interaction logic for AuditView
+ ///
+ public partial class AuditView : UserControl
+ {
+ public AuditView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml
new file mode 100644
index 0000000..13fe0df
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml.cs
new file mode 100644
index 0000000..e0ae02a
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Views
+{
+ ///
+ /// Interaction logic for Configuration
+ ///
+ public partial class ConfigurationView : UserControl
+ {
+ public ConfigurationView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml
new file mode 100644
index 0000000..e645813
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml.cs
new file mode 100644
index 0000000..7822c39
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Views
+{
+ ///
+ /// Interaction logic for HelpView
+ ///
+ public partial class HelpView : UserControl
+ {
+ public HelpView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml
new file mode 100644
index 0000000..8f930c1
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml.cs
new file mode 100644
index 0000000..67055fc
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Views
+{
+ ///
+ /// Interaction logic for ImportScopeView
+ ///
+ public partial class ImportScopeView : UserControl
+ {
+ public ImportScopeView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml
new file mode 100644
index 0000000..2d9c302
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml.cs
new file mode 100644
index 0000000..417ab87
--- /dev/null
+++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace Dionach.ShareAudit.Modules.UserInterface.Views
+{
+ ///
+ /// Interaction logic for ViewA.xaml
+ ///
+ public partial class WelcomeView : UserControl
+ {
+ public WelcomeView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/App.xaml b/src/Dionach.ShareAudit/App.xaml
new file mode 100644
index 0000000..75d3e71
--- /dev/null
+++ b/src/Dionach.ShareAudit/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/Dionach.ShareAudit/App.xaml.cs b/src/Dionach.ShareAudit/App.xaml.cs
new file mode 100644
index 0000000..9c1f1f7
--- /dev/null
+++ b/src/Dionach.ShareAudit/App.xaml.cs
@@ -0,0 +1,90 @@
+using Dionach.ShareAudit.Modules.Services;
+using Dionach.ShareAudit.Modules.UserInterface;
+using Dionach.ShareAudit.Views;
+using Prism.Ioc;
+using Prism.Modularity;
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Dionach.ShareAudit
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App
+ {
+ protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
+ {
+ moduleCatalog.AddModule();
+ moduleCatalog.AddModule();
+ }
+
+ protected override Window CreateShell()
+ {
+ return Container.Resolve();
+ }
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ SetupExceptionHandling();
+ }
+
+ protected override void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ }
+
+ private static void ShowUnhandledException(Exception e, string source)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine($"An application error occurred in {source}");
+ sb.AppendLine();
+ sb.AppendLine($"Error: {e.GetType().Name} - {e.Message}");
+ sb.AppendLine($"Stack Trace:");
+ sb.AppendLine(e.StackTrace);
+ while (e.InnerException != null)
+ {
+ e = e.InnerException;
+
+ sb.AppendLine();
+ sb.AppendLine($"Error: {e.GetType().Name} - {e.Message}");
+ sb.AppendLine($"Stack Trace:");
+ sb.AppendLine(e.StackTrace);
+ }
+
+ new ErrorWindow(sb.ToString()).ShowDialog();
+
+ Application.Current.Shutdown();
+ }
+
+ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ throw new NotImplementedException();
+ }
+
+ private void SetupExceptionHandling()
+ {
+#if !DEBUG
+ AppDomain.CurrentDomain.UnhandledException += (s, e) =>
+ {
+ ShowUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");
+ };
+
+ DispatcherUnhandledException += (s, e) =>
+ {
+ e.Handled = true;
+ ShowUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
+ };
+
+ TaskScheduler.UnobservedTaskException += (s, e) =>
+ {
+ ShowUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
+ };
+
+#endif
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/Dionach.ShareAudit.csproj b/src/Dionach.ShareAudit/Dionach.ShareAudit.csproj
new file mode 100644
index 0000000..6731dcf
--- /dev/null
+++ b/src/Dionach.ShareAudit/Dionach.ShareAudit.csproj
@@ -0,0 +1,129 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}
+ WinExe
+ Properties
+ Dionach.ShareAudit
+ ShareAudit
+ v4.6.1
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ PackageReference
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ Icon.ico
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+ ErrorWindow.xaml
+
+
+ MainWindow.xaml
+ Code
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+ 3.3.3
+
+
+
+
+
+ {F5BFA34B-3CDE-4C77-9162-96666303FDEA}
+ Dionach.ShareAudit.Modules.Services
+
+
+ {28cf3837-ff58-463b-af81-e6b0039de55f}
+ Dionach.ShareAudit.Modules.UserInterface
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit/FodyWeavers.xml b/src/Dionach.ShareAudit/FodyWeavers.xml
new file mode 100644
index 0000000..a5dcf04
--- /dev/null
+++ b/src/Dionach.ShareAudit/FodyWeavers.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit/FodyWeavers.xsd b/src/Dionach.ShareAudit/FodyWeavers.xsd
new file mode 100644
index 0000000..44a5374
--- /dev/null
+++ b/src/Dionach.ShareAudit/FodyWeavers.xsd
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+ A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks
+
+
+
+
+ A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.
+
+
+
+
+ A list of unmanaged 32 bit assembly names to include, delimited with line breaks.
+
+
+
+
+ A list of unmanaged 64 bit assembly names to include, delimited with line breaks.
+
+
+
+
+ The order of preloaded assemblies, delimited with line breaks.
+
+
+
+
+
+ This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.
+
+
+
+
+ Controls if .pdbs for reference assemblies are also embedded.
+
+
+
+
+ Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.
+
+
+
+
+ As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.
+
+
+
+
+ Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.
+
+
+
+
+ Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.
+
+
+
+
+ A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |
+
+
+
+
+ A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.
+
+
+
+
+ A list of unmanaged 32 bit assembly names to include, delimited with |.
+
+
+
+
+ A list of unmanaged 64 bit assembly names to include, delimited with |.
+
+
+
+
+ The order of preloaded assemblies, delimited with |.
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit/Icon.ico b/src/Dionach.ShareAudit/Icon.ico
new file mode 100644
index 0000000..5bce6bc
Binary files /dev/null and b/src/Dionach.ShareAudit/Icon.ico differ
diff --git a/src/Dionach.ShareAudit/Images/Icon.ico b/src/Dionach.ShareAudit/Images/Icon.ico
new file mode 100644
index 0000000..5bce6bc
Binary files /dev/null and b/src/Dionach.ShareAudit/Images/Icon.ico differ
diff --git a/src/Dionach.ShareAudit/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f61d7d1
--- /dev/null
+++ b/src/Dionach.ShareAudit/Properties/AssemblyInfo.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following set of attributes.
+// Change these attribute values to modify the information associated with an assembly.
+[assembly: AssemblyTitle("Dionach.ShareAudit")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dionach.ShareAudit")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you
+// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// In order to begin building localizable applications, set
+// CultureYouAreCodingWith in your .csproj file inside a . For
+// example, if you are using US english in your source files, set the to en-US. Then
+// uncomment the NeutralResourceLanguage attribute below. Update the "en-US" in the line below to
+// match the UICulture setting in the project file.
+[assembly: NeutralResourcesLanguage("en-GB"/*, UltimateResourceFallbackLocation.Satellite*/)]
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
+]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version Minor Version Build Number Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers by using the '*'
+// as shown below: [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.0.0.0")]
+[assembly: AssemblyFileVersion("3.0.0.0")]
+[assembly: CLSCompliant(false)]
diff --git a/src/Dionach.ShareAudit/Properties/Resources.Designer.cs b/src/Dionach.ShareAudit/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..45ba0a2
--- /dev/null
+++ b/src/Dionach.ShareAudit/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dionach.ShareAudit.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/Properties/Resources.resx b/src/Dionach.ShareAudit/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/src/Dionach.ShareAudit/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit/Properties/Settings.Designer.cs b/src/Dionach.ShareAudit/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..eb5e823
--- /dev/null
+++ b/src/Dionach.ShareAudit/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Dionach.ShareAudit.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/Properties/Settings.settings b/src/Dionach.ShareAudit/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/src/Dionach.ShareAudit/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Dionach.ShareAudit/ViewModels/MainWindowViewModel.cs b/src/Dionach.ShareAudit/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..c9a7c8c
--- /dev/null
+++ b/src/Dionach.ShareAudit/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,19 @@
+using Prism.Mvvm;
+
+namespace Dionach.ShareAudit.ViewModels
+{
+ public class MainWindowViewModel : BindableBase
+ {
+ private string _title = $"Share Audit - {typeof(MainWindowViewModel).Assembly.GetName().Version}";
+
+ public MainWindowViewModel()
+ {
+ }
+
+ public string Title
+ {
+ get => _title;
+ set => SetProperty(ref _title, value);
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/Views/ErrorWindow.xaml b/src/Dionach.ShareAudit/Views/ErrorWindow.xaml
new file mode 100644
index 0000000..4c05ea6
--- /dev/null
+++ b/src/Dionach.ShareAudit/Views/ErrorWindow.xaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit/Views/ErrorWindow.xaml.cs b/src/Dionach.ShareAudit/Views/ErrorWindow.xaml.cs
new file mode 100644
index 0000000..fdfb77c
--- /dev/null
+++ b/src/Dionach.ShareAudit/Views/ErrorWindow.xaml.cs
@@ -0,0 +1,21 @@
+using System.Windows;
+
+namespace Dionach.ShareAudit.Views
+{
+ ///
+ /// Interaction logic for ErrorWindow.xaml
+ ///
+ public partial class ErrorWindow : Window
+ {
+ public ErrorWindow(string errorText)
+ {
+ InitializeComponent();
+ _errorTextBox.Text = errorText;
+ }
+
+ private void ExitButton_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+ }
+}
diff --git a/src/Dionach.ShareAudit/Views/MainWindow.xaml b/src/Dionach.ShareAudit/Views/MainWindow.xaml
new file mode 100644
index 0000000..70aff04
--- /dev/null
+++ b/src/Dionach.ShareAudit/Views/MainWindow.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/src/Dionach.ShareAudit/Views/MainWindow.xaml.cs b/src/Dionach.ShareAudit/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000..0664d92
--- /dev/null
+++ b/src/Dionach.ShareAudit/Views/MainWindow.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows;
+
+namespace Dionach.ShareAudit.Views
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}