diff --git a/README.md b/README.md index da34903..0602be2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ # SymlinkCreator -A GUI app for creating symlinks, based on MKLink +SymlinkCreator is a GUI app for creating symbolic links (symlinks), and it is based on `mklink`. You can create multiple symlinks at a time. + +## Use cases +- Suppose, you have a collection of several songs sorted by artists and albums in your PC. You might want a separate collection of your most favorite songs which you will store in your mobile devices. In this scenario, the traditional shortcut option through Windows Explorer right-click context menu is not enough, because you cannot copy the actual file contents by copying the traditional shortcut files (*\*.lnk*). You might consider duplicating the files which you will store in your mobile devices. But it will waste your storage space of your PC. In this case, SymlinkCreator will come in handy. You can easily create the separate collection of songs and transfer them to your mobile devices, without wasting your PC's storage space. + +- Suppose, you have a special folder which is linked to your online storage like Google Drive. You might want some specific files to be backed up from other folders. A traditional shortcut file is not helpful here to back up those files. In this scenario, you can use SymlinkCreator for the backup purpose without duplicating those files in the special folder. + +## What SymlinkCreator does +SymlinkCreator creates *symlinks* which is an NTFS feature. Unlink the traditional shortcut files (*\*.lnk*), symlinks do not have any *file size*. While symlinks may be called advanced shortcut files, but they appear to be real files. Unlike duplicated files, symlinks do not waste your storage space. Currently, SymlinkCreator only works for files, not folders. I might add support for folders in the future. + +## How SymlinkCreator works +SymlinkCreator uses `mklink` command to create symlinks. SymlinkCreator first creates a script file which contains `mklink` command lines, and execute it. SymlinkCreator works in Windows Vista, Windows 7 and Windows 10. It does not work in Windows XP because of the lack of `mklink` command. + +## How to use SymlinkCreator +![Screenshot](SymlinkCreator/_ReadMe/Screenshot.png "Screenshot of SymlinkCreator") +- At the `Source file list`, you can add files which will be copied in `Destination path` as symlinks. +- Using SymlinkCreator's drag-n-drop feature, you can easily create multiple symlinks at a time. +- Tick `Use relative path if possible` option to use relative paths while creating symlinks. In this case, relative paths will be used if both source files and destination files are in the same drive. +- Tick `Retain script file after execution` option if you want to save the script file for later use like logging purpose. + +## Why SymlinkCreator needs administrative rights +It has been stated before that SymlinkCreator uses `mklink` command to create symlinks. For some reasons, Microsoft decided symlink creation to be an administrative privilege. Later Microsoft has changed the decision and allowed Windows 10 Creators Update to create symlinks without requiring administrative rights. But for the compatibility reason, SymlinkCreator asks for administrative rights. SymlinkCreator may be improved later by checking specific Windows versions and by not asking for unnecessary administrative rights. diff --git a/SymlinkCreator/App.config b/SymlinkCreator/App.config new file mode 100644 index 0000000..8609e96 --- /dev/null +++ b/SymlinkCreator/App.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/SymlinkCreator/App.xaml b/SymlinkCreator/App.xaml new file mode 100644 index 0000000..c61b0a9 --- /dev/null +++ b/SymlinkCreator/App.xaml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/SymlinkCreator/App.xaml.cs b/SymlinkCreator/App.xaml.cs new file mode 100644 index 0000000..c8412f8 --- /dev/null +++ b/SymlinkCreator/App.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace SymlinkCreator +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} \ No newline at end of file diff --git a/SymlinkCreator/Properties/Annotations.cs b/SymlinkCreator/Properties/Annotations.cs new file mode 100644 index 0000000..87cf259 --- /dev/null +++ b/SymlinkCreator/Properties/Annotations.cs @@ -0,0 +1,1118 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace SymlinkCreator.Properties +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class CanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class NotNullAttribute : Attribute + { + } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute + { + } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute + { + } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute(string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute + { + } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() + { + } + + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) + { + } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) + { + } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute + { + } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof (Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() + { + } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute + { + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute + { + } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() + { + } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | + AttributeTargets.Method)] + public sealed class ProvidesContextAttribute : Attribute + { + } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() + { + } + + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + public string BasePath { get; private set; } + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute + { + } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// > + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() + { + } + + public AspMvcActionAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() + { + } + + public AspMvcAreaAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() + { + } + + public AspMvcControllerAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcViewComponentAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewComponentViewAttribute : Attribute + { + } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() + { + } + + public HtmlElementAttributesAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute + { + } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute + { + } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute + { + } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute + { + } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute + { + } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RegexPatternAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute + { + } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute(string tagName, Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + public string TagName { get; private set; } + public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute(string type, string fieldName) + { + Type = type; + FieldName = fieldName; + } + + public string Type { get; private set; } + public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute + { + } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class NoReorder : Attribute + { + } +} \ No newline at end of file diff --git a/SymlinkCreator/Properties/AssemblyInfo.cs b/SymlinkCreator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..da656f8 --- /dev/null +++ b/SymlinkCreator/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +using System.Reflection; +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("Symlink Creator")] +[assembly: AssemblyDescription("A GUI app for creating symlinks")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Arnob Paul")] +[assembly: AssemblyProduct("Symlink Creator")] +[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)] + +//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-US", 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")] +[assembly: AssemblyFileVersion("1.0.0")] \ No newline at end of file diff --git a/SymlinkCreator/SymlinkCreator.csproj b/SymlinkCreator/SymlinkCreator.csproj new file mode 100644 index 0000000..333db93 --- /dev/null +++ b/SymlinkCreator/SymlinkCreator.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0} + WinExe + Properties + SymlinkCreator + SymlinkCreator + v4.6.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + resources\icons\MainIcon.ico + + + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + + + MainWindow.xaml + + + + + + + + Code + + + + + + + + + + + + Designer + MSBuild:Compile + + + + + \ No newline at end of file diff --git a/SymlinkCreator/SymlinkCreator.sln b/SymlinkCreator/SymlinkCreator.sln new file mode 100644 index 0000000..a3d7f68 --- /dev/null +++ b/SymlinkCreator/SymlinkCreator.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymlinkCreator", "SymlinkCreator.csproj", "{D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymlinkCreatorTests", "..\SymlinkCreatorTests\SymlinkCreatorTests.csproj", "{0446C461-5775-4B25-815C-FC3C99D2A50C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Debug|x86.Build.0 = Debug|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Release|Any CPU.Build.0 = Release|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Release|x86.ActiveCfg = Release|Any CPU + {D9331BF2-AE1F-4E4D-AB46-C9E99189ACE0}.Release|x86.Build.0 = Release|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.ActiveCfg = Debug|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Debug|x86.Build.0 = Debug|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|Any CPU.Build.0 = Release|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.ActiveCfg = Release|Any CPU + {0446C461-5775-4B25-815C-FC3C99D2A50C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SymlinkCreator/SymlinkCreator.sln.DotSettings b/SymlinkCreator/SymlinkCreator.sln.DotSettings new file mode 100644 index 0000000..ecad4dd --- /dev/null +++ b/SymlinkCreator/SymlinkCreator.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/SymlinkCreator/_ReadMe/Screenshot.png b/SymlinkCreator/_ReadMe/Screenshot.png new file mode 100644 index 0000000..9fcad2d Binary files /dev/null and b/SymlinkCreator/_ReadMe/Screenshot.png differ diff --git a/SymlinkCreator/core/ApplicationConfiguration.cs b/SymlinkCreator/core/ApplicationConfiguration.cs new file mode 100644 index 0000000..f1bfeb2 --- /dev/null +++ b/SymlinkCreator/core/ApplicationConfiguration.cs @@ -0,0 +1,88 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace SymlinkCreator.core +{ + internal static class ApplicationConfiguration + { + #region assembly properties + + private const int ApplicationVersionNumberCount = 3; + + private static string _applicationGuid; + public static string ApplicationGuid + { + get + { + if (!string.IsNullOrEmpty(_applicationGuid)) + return _applicationGuid; + + _applicationGuid = Assembly.GetExecutingAssembly().GetCustomAttribute().Value; + return _applicationGuid; + } + } + + private static string _applicationName; + public static string ApplicationName + { + get + { + if (!string.IsNullOrEmpty(_applicationName)) + return _applicationName; + + _applicationName = Assembly.GetExecutingAssembly().GetCustomAttribute().Title; + return _applicationName; + } + } + + private static string _applicationVersion; + public static string ApplicationVersion + { + get + { + if (!string.IsNullOrEmpty(_applicationVersion)) + return _applicationVersion; + + string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + string[] versionNumbers = version.Split('.'); + StringBuilder versionStringBuilder = new StringBuilder(version.Length); + for (int i = 0; (i < versionNumbers.Length && i < ApplicationVersionNumberCount); i++) + { + versionStringBuilder.Append(versionNumbers[i]); + versionStringBuilder.Append('.'); + } + if (versionStringBuilder.Length != 0) + versionStringBuilder.Length--; + + _applicationVersion = versionStringBuilder.ToString(); + + return _applicationVersion; + } + } + + private static string _applicationCompany; + public static string ApplicationCompany + { + get + { + if (!string.IsNullOrEmpty(_applicationCompany)) + return _applicationCompany; + + _applicationCompany = + Assembly.GetExecutingAssembly().GetCustomAttribute().Company; + return _applicationCompany; + } + } + + #endregion + + + #region web addresses + + public static string CompanyWebAddress => "https://github.com/arnobpl"; + + #endregion + } +} \ No newline at end of file diff --git a/SymlinkCreator/core/ScriptExecutor.cs b/SymlinkCreator/core/ScriptExecutor.cs new file mode 100644 index 0000000..373a9a5 --- /dev/null +++ b/SymlinkCreator/core/ScriptExecutor.cs @@ -0,0 +1,49 @@ +using System.Diagnostics; +using System.IO; + +namespace SymlinkCreator.core +{ + class ScriptExecutor : StreamWriter + { + #region members + + private readonly string _fileName; + + #endregion + + + #region constructor + + public ScriptExecutor(string fileName) : base(fileName) + { + this._fileName = fileName; + } + + #endregion + + + #region methods + + /// + /// Executes the script as admin. It blocks the control flow while executing. + /// + public void ExecuteAsAdmin() + { + Close(); + + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + FileName = _fileName, + UseShellExecute = true, + Verb = "runas" + }; + Process process = Process.Start(processStartInfo); + if (process == null) return; + + process.WaitForExit(); + process.Close(); + } + + #endregion + } +} \ No newline at end of file diff --git a/SymlinkCreator/core/SymlinkAgent.cs b/SymlinkCreator/core/SymlinkAgent.cs new file mode 100644 index 0000000..9c331fe --- /dev/null +++ b/SymlinkCreator/core/SymlinkAgent.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace SymlinkCreator.core +{ + public class SymlinkAgent + { + #region members + + private readonly List _sourceFileList; + private string _destinationPath; + private readonly bool _shouldUseRelativePath; + private readonly bool _shouldRetainScriptFile; + + private string[] _splittedDestinationPath; + + #endregion + + + #region constructor + + public SymlinkAgent(IEnumerable sourceFileList, string destinationPath, + bool shouldUseRelativePath = true, bool shouldRetainScriptFile = false) + { + this._sourceFileList = sourceFileList.ToList(); + this._destinationPath = destinationPath; + this._shouldUseRelativePath = shouldUseRelativePath; + this._shouldRetainScriptFile = shouldRetainScriptFile; + } + + #endregion + + + #region methods + + public void CreateSymlinks() + { + // check for destination path + if (!Directory.Exists(_destinationPath)) + { + Debug.WriteLine("Destination path does not exist: " + _destinationPath); + return; + } + + // remove the last '\' character from the path if exists + if (_destinationPath[_destinationPath.Length - 1] == '\\') + _destinationPath = _destinationPath.Substring(0, _destinationPath.Length - 1); + + _splittedDestinationPath = GetSplittedPath(_destinationPath); + + string scriptFileName = ApplicationConfiguration.ApplicationName + "_" + + DateTime.Now.Ticks.ToString() + ".bat"; + ScriptExecutor scriptExecutor = new ScriptExecutor(scriptFileName); + + scriptExecutor.WriteLine("cd \"" + _destinationPath + "\""); + + foreach (string sourceFilePath in _sourceFileList) + { + string[] splittedSourceFilePath = GetSplittedPath(sourceFilePath); + + string commandLineTargetPath = sourceFilePath; + if (_shouldUseRelativePath) + { + // check if both root drives are same + if (splittedSourceFilePath.First() == _splittedDestinationPath.First()) + { + commandLineTargetPath = GetRelativePath(_splittedDestinationPath, splittedSourceFilePath); + } + } + + scriptExecutor.WriteLine("mklink \"" + splittedSourceFilePath.Last() + "\" \"" + + commandLineTargetPath + "\""); + } + + scriptExecutor.ExecuteAsAdmin(); + + if (!_shouldRetainScriptFile) + File.Delete(scriptFileName); + } + + #endregion + + + #region helper methods + + private string[] GetSplittedPath(string path) + { + return path.Split('\\'); + } + + private string GetRelativePath(string[] splittedCurrentPath, string[] splittedTargetPath) + { + List splittedCurrentPathList = splittedCurrentPath.ToList(); + List splittedTargetPathList = splittedTargetPath.ToList(); + + while (splittedCurrentPathList.Any() && splittedTargetPathList.Any()) + { + if (splittedCurrentPathList.First() == splittedTargetPathList.First()) + { + splittedCurrentPathList.RemoveAt(0); + splittedTargetPathList.RemoveAt(0); + } + else + { + break; + } + } + + StringBuilder relativePathStringBuilder = new StringBuilder(); + + for (int i = 0; i < splittedCurrentPathList.Count; i++) + { + relativePathStringBuilder.Append("..\\"); + } + + foreach (string splittedPath in splittedTargetPathList) + { + relativePathStringBuilder.Append(splittedPath); + relativePathStringBuilder.Append('\\'); + } + + if (relativePathStringBuilder[relativePathStringBuilder.Length - 1] == '\\') + relativePathStringBuilder.Length--; + + return relativePathStringBuilder.ToString(); + } + + private string GetRelativePath(string currentPath, string targetPath) + { + return GetRelativePath(GetSplittedPath(currentPath), GetSplittedPath(targetPath)); + } + + #endregion + } +} \ No newline at end of file diff --git a/SymlinkCreator/resources/icons/MainIcon.ico b/SymlinkCreator/resources/icons/MainIcon.ico new file mode 100644 index 0000000..a84bfc3 Binary files /dev/null and b/SymlinkCreator/resources/icons/MainIcon.ico differ diff --git a/SymlinkCreator/ui/mainWindow/MainWindow.xaml b/SymlinkCreator/ui/mainWindow/MainWindow.xaml new file mode 100644 index 0000000..c8f8757 --- /dev/null +++ b/SymlinkCreator/ui/mainWindow/MainWindow.xaml @@ -0,0 +1,129 @@ + + + 5 + 80 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +