Port remaining members of ErrObject, Interaction, SpecialDirectories (dotnet/corefx...
authorCharles Stoner <chucks@microsoft.com>
Tue, 27 Aug 2019 18:17:58 +0000 (11:17 -0700)
committerGitHub <noreply@github.com>
Tue, 27 Aug 2019 18:17:58 +0000 (11:17 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/063568705557b469606eef1069946484d78034ae

15 files changed:
src/libraries/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/ErrObject.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Unix.vb [deleted file]
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Windows.vb [deleted file]
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/SpecialDirectories.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Helpers/NativeMethods.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Helpers/NativeTypes.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Information.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Interaction.vb
src/libraries/Microsoft.VisualBasic.Core/src/Resources/Strings.resx
src/libraries/Microsoft.VisualBasic.Core/tests/ErrObjectTests.cs
src/libraries/Microsoft.VisualBasic.Core/tests/InteractionTests.cs
src/libraries/Microsoft.VisualBasic.Core/tests/Microsoft/VisualBasic/FileIO/SpecialDirectoriesTests.cs

index 614c27a..0993504 100644 (file)
@@ -306,8 +306,11 @@ namespace Microsoft.VisualBasic
         internal ErrObject() { }
         public string Description { get { throw null; } set { } }
         public int Erl { get { throw null; } }
+        public int HelpContext { get { throw null; } set { } }
+        public string HelpFile { get { throw null; } set { } }
         public int LastDllError { get { throw null; } }
         public int Number { get { throw null; } set { } }
+        public string Source { get { throw null; } set { } }
         public void Clear() { }
         public System.Exception GetException() { throw null; }
         public void Raise(int Number, object Source = null, object Description = null, object HelpFile = null, object HelpContext = null) { }
@@ -458,6 +461,7 @@ namespace Microsoft.VisualBasic
     public sealed partial class Information
     {
         internal Information() { }
+        public static int Erl() { throw null; }
         public static Microsoft.VisualBasic.ErrObject Err() { throw null; }
         public static bool IsArray(object VarName) { throw null; }
         public static bool IsDate(object Expression) { throw null; }
@@ -479,12 +483,25 @@ namespace Microsoft.VisualBasic
     public sealed partial class Interaction
     {
         internal Interaction() { }
+        public static void AppActivate(int ProcessId) { }
+        public static void AppActivate(string Title) { }
         public static void Beep() { }
         public static object CallByName(object ObjectRef, string ProcName, Microsoft.VisualBasic.CallType UseCallType, params object[] Args) { throw null; }
         public static object Choose(double Index, params object[] Choice) { throw null; }
+        public static string Command() { throw null; }
         public static object CreateObject(string ProgId, string ServerName = "") { throw null; }
+        public static void DeleteSetting(string AppName, string Section = null, string Key = null) { }
+        public static string Environ(string Expression) { throw null; }
+        public static string Environ(int Expression) { throw null; }
+        public static string[,] GetAllSettings(string AppName, string Section) { throw null; }
+        public static object GetObject(string PathName = null, string Class = null) { throw null; }
+        public static string GetSetting(string AppName, string Section, string Key, string Default = "") { throw null; }
         public static object IIf(bool Expression, object TruePart, object FalsePart) { throw null; }
+        public static string InputBox(string Prompt, string Title = "", string DefaultResponse = "", int XPos = -1, int YPos = -1) { throw null; }
+        public static MsgBoxResult MsgBox(object Prompt, MsgBoxStyle Buttons = MsgBoxStyle.ApplicationModal, object Title = null) { throw null; }
         public static string Partition(long Number, long Start, long Stop, long Interval) { throw null; }
+        public static void SaveSetting(string AppName, string Section, string Key, string Setting) { }
+        public static int Shell(string PathName, AppWinStyle Style = AppWinStyle.MinimizedFocus, bool Wait = false, int Timeout = -1) { throw null; }
         public static object Switch(params object[] VarExpr) { throw null; }
     }
     public enum MsgBoxResult
index fcf45df..96609c1 100644 (file)
@@ -11,6 +11,7 @@
     <WarningsNotAsErrors>42025</WarningsNotAsErrors>
     <DefineConstants>$(DefineConstants),LATEBINDING=True</DefineConstants>
     <DefineConstants Condition="'$(TargetsWindows)' == 'true'">$(DefineConstants),PLATFORM_WINDOWS=True</DefineConstants>
+    <DefineConstants Condition="'$(TargetGroup)' == 'uap'">$(DefineConstants),PLATFORM_UAP=True</DefineConstants>
     <NoWarn Condition="'$(TargetsWindows)' != 'true'">$(NoWarn);CA1823</NoWarn> <!-- Avoid unused fields warnings in Unix build -->
     <AssemblyName>Microsoft.VisualBasic.Core</AssemblyName>
     <NoStdLib>true</NoStdLib>
     <Configurations>netcoreapp-Debug;netcoreapp-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release</Configurations>
   </PropertyGroup>
   <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
-    <Compile Include="Microsoft\VisualBasic\FileIO\FileSystem.Windows.vb" />
     <Compile Include="Microsoft\VisualBasic\Helpers\NativeMethods.vb" />
     <Compile Include="Microsoft\VisualBasic\Helpers\NativeTypes.vb" />
     <Compile Include="Microsoft\VisualBasic\Helpers\SafeNativeMethods.vb" />
     <Compile Include="Microsoft\VisualBasic\Helpers\UnsafeNativeMethods.vb" />
   </ItemGroup>
-    <ItemGroup Condition="'$(TargetsWindows)' != 'true'">
-    <Compile Include="Microsoft\VisualBasic\FileIO\FileSystem.Unix.vb" />
-  </ItemGroup>
   <ItemGroup>
     <Compile Include="Microsoft\VisualBasic\Collection.vb" />
     <Compile Include="Microsoft\VisualBasic\ComClassAttribute.vb" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Microsoft.Win32.Primitives" />
+    <Reference Condition="'$(TargetGroup)' != 'uap'" Include="Microsoft.Win32.Registry" />
     <Reference Include="System.Collections" />
+    <Reference Include="System.Collections.NonGeneric" />
     <Reference Include="System.Collections.Specialized" />
     <Reference Include="System.ComponentModel" />
     <Reference Include="System.ComponentModel.Primitives" />
index 10c6b4e..346c655 100644 (file)
@@ -22,6 +22,13 @@ Namespace Microsoft.VisualBasic
         Private m_ClearOnCapture As Boolean
         Private m_DescriptionIsSet As Boolean
 
+        Private m_curSource As String
+        Private m_SourceIsSet As Boolean
+        Private m_curHelpFile As String
+        Private m_curHelpContext As Integer
+        Private m_HelpFileIsSet As Boolean
+        Private m_HelpContextIsSet As Boolean
+
         Friend Sub New()
             Me.Clear() 'need to do this so the fields are set to Empty string, not Nothing
         End Sub
@@ -57,6 +64,30 @@ Namespace Microsoft.VisualBasic
             End Set
         End Property
 
+        Public Property Source() As String
+            Get
+                'Return the current Source if we've already calculated it.
+                If m_SourceIsSet Then
+                    Return m_curSource
+                End If
+
+                If Not m_curException Is Nothing Then
+                    Me.Source = m_curException.Source
+                    Return m_curSource
+                Else
+                    'The default case.  NOTE:  falling into the default does not "Set" the property.
+                    'We only get here if the Err object was previously cleared.
+                    '
+                    Return ""
+                End If
+            End Get
+
+            Set(ByVal Value As String)
+                m_curSource = Value
+                m_SourceIsSet = True
+            End Set
+        End Property
+
         ''' <summary>
         ''' Determines what the correct error description should be.
         ''' If we don't have an exception that we are responding to then
@@ -113,6 +144,101 @@ Namespace Microsoft.VisualBasic
             End Set
         End Property
 
+        Public Property HelpFile() As String
+            Get
+                If m_HelpFileIsSet Then
+                    Return m_curHelpFile
+                End If
+
+                If Not m_curException Is Nothing Then
+                    ParseHelpLink(m_curException.HelpLink)
+                    Return m_curHelpFile
+                Else
+                    'The default case.  NOTE:  falling into the default does not "Set" the property.
+                    'We only get here if the Err object was previously cleared.
+                    '
+                    Return ""
+                End If
+            End Get
+
+            Set(ByVal Value As String)
+                m_curHelpFile = Value
+
+                m_HelpFileIsSet = True
+            End Set
+        End Property
+
+        Private Function MakeHelpLink(ByVal HelpFile As String, ByVal HelpContext As Integer) As String
+            Return HelpFile & "#" & CStr(HelpContext)
+        End Function
+
+        Private Sub ParseHelpLink(ByVal HelpLink As String)
+
+            Diagnostics.Debug.Assert((Not m_HelpContextIsSet) OrElse (Not m_HelpFileIsSet), "Why is this getting called?")
+
+            If HelpLink Is Nothing OrElse HelpLink.Length = 0 Then
+
+                If Not m_HelpContextIsSet Then
+                    Me.HelpContext = 0
+                End If
+                If Not m_HelpFileIsSet Then
+                    Me.HelpFile = ""
+                End If
+
+            Else
+
+                Dim iContext As Integer = m_InvariantCompareInfo.IndexOf(HelpLink, "#", Globalization.CompareOptions.Ordinal)
+
+                If iContext <> -1 Then
+                    If Not m_HelpContextIsSet Then
+                        If iContext < HelpLink.Length Then
+                            Me.HelpContext = CInt(HelpLink.Substring(iContext + 1))
+                        Else
+                            Me.HelpContext = 0
+                        End If
+                    End If
+                    If Not m_HelpFileIsSet Then
+                        Me.HelpFile = HelpLink.Substring(0, iContext)
+                    End If
+                Else
+                    If Not m_HelpContextIsSet Then
+                        Me.HelpContext = 0
+                    End If
+                    If Not m_HelpFileIsSet Then
+                        Me.HelpFile = HelpLink
+                    End If
+                End If
+
+            End If
+
+        End Sub
+
+        Public Property HelpContext() As Integer
+            Get
+                If m_HelpContextIsSet Then
+                    Return m_curHelpContext
+                End If
+
+                If Not m_curException Is Nothing Then
+                    ParseHelpLink(m_curException.HelpLink)
+                    Return m_curHelpContext
+
+                Else
+                    'The default case.  NOTE:  falling into the default does not "Set" the property.
+                    'We only get here if the Err object was previously cleared.
+                    '
+                    Return 0
+                End If
+
+                Return m_curHelpContext
+            End Get
+
+            Set(ByVal Value As Integer)
+                m_curHelpContext = Value
+                m_HelpContextIsSet = True
+            End Set
+        End Property
+
         Public Function GetException() As Exception
             Return m_curException
         End Function
@@ -124,6 +250,12 @@ Namespace Microsoft.VisualBasic
         Public Sub Clear()
             m_curException = Nothing
             m_curNumber = 0
+            m_curSource = ""
+            m_curHelpFile = ""
+            m_curHelpContext = 0
+            m_SourceIsSet = False
+            m_HelpFileIsSet = False
+            m_HelpContextIsSet = False
             m_curDescription = ""
             m_curErl = 0
             m_NumberIsSet = False
@@ -151,6 +283,31 @@ Namespace Microsoft.VisualBasic
             End If
             Me.Number = Number
 
+            If Not Source Is Nothing Then
+                Me.Source = CStr(Source)
+            Else
+                ' .NET Framework uses VBHost.GetWindowTitle() if available
+                ' but the VBHost type is not accessible here.
+                Dim FullName As String
+                Dim CommaPos As Integer
+
+                FullName = System.Reflection.Assembly.GetCallingAssembly().FullName
+                CommaPos = InStr(FullName, ",")
+                If CommaPos < 1 Then
+                    Me.Source = FullName
+                Else
+                    Me.Source = Left(FullName, CommaPos - 1)
+                End If
+            End If
+
+            If Not HelpFile Is Nothing Then
+                Me.HelpFile = CStr(HelpFile)
+            End If
+
+            If Not HelpContext Is Nothing Then
+                Me.HelpContext = CInt(HelpContext)
+            End If
+
             If Not Description Is Nothing Then
                 Me.Description = CStr(Description)
             ElseIf Not m_DescriptionIsSet Then
@@ -160,6 +317,8 @@ Namespace Microsoft.VisualBasic
 
             Dim e As Exception
             e = MapNumberToException(m_curNumber, m_curDescription)
+            e.Source = m_curSource
+            e.HelpLink = MakeHelpLink(m_curHelpFile, m_curHelpContext)
             m_ClearOnCapture = False
             Throw e
         End Sub
diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Unix.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Unix.vb
deleted file mode 100644 (file)
index ea428ee..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-' Licensed to the .NET Foundation under one or more agreements.
-' The .NET Foundation licenses this file to you under the MIT license.
-' See the LICENSE file in the project root for more information.
-Option Explicit On
-Option Strict On
-
-Imports System
-
-Namespace Microsoft.VisualBasic.FileIO
-    Partial Public Class FileSystem
-        Private Shared Sub ShellCopyOrMove(operation As CopyOrMove, directory As FileOrDirectory, sourceDirectoryFullPath As String, targetDirectoryFullPath As String, showUI As UIOptionInternal, onUserCancel As UICancelOption)
-            Throw New PlatformNotSupportedException(SR.NoShellCopyOrMove)
-        End Sub
-
-        Private Shared Sub ShellDelete(directoryFullPath As String, showUI As UIOptionInternal, recycle As RecycleOption, onUserCancel As UICancelOption, directory As FileOrDirectory)
-            Throw New PlatformNotSupportedException(SR.NoShellDelete)
-        End Sub
-
-        ''' <summary>
-        ''' Stub to prevent compile error, this will not get called on non Windows OS's
-        ''' </summary>
-        ''' <param name="sourceFileFullPath"></param>
-        ''' <param name="destinationFileFullPath"></param>
-        Private Shared Sub WinNTCopyOrMove(sourceFileFullPath As String, destinationFileFullPath As String)
-            Throw New PlatformNotSupportedException()
-        End Sub
-    End Class
-
-End Namespace
diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Windows.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/FileIO/FileSystem.Windows.vb
deleted file mode 100644 (file)
index a085476..0000000
+++ /dev/null
@@ -1,340 +0,0 @@
-' Licensed to the .NET Foundation under one or more agreements.
-' The .NET Foundation licenses this file to you under the MIT license.
-' See the LICENSE file in the project root for more information.
-Option Strict On
-Option Explicit On
-
-Imports System
-Imports System.ComponentModel
-Imports System.Diagnostics
-Imports System.Security
-Imports System.Text
-
-Imports Microsoft.VisualBasic.CompilerServices
-Imports Microsoft.VisualBasic.CompilerServices.NativeMethods
-Imports Microsoft.VisualBasic.CompilerServices.NativeTypes
-
-'''' IMPORTANT: Changes made to public interface of FileSystem should be reflected in FileSystemProxy.vb.
-
-Namespace Microsoft.VisualBasic.FileIO
-
-    Partial Public Class FileSystem
-        ''' <summary>
-        ''' Sets relevant flags on the SHFILEOPSTRUCT and calls SHFileOperation to copy move file / directory.
-        ''' </summary>
-        ''' <param name="Operation">Copy or move.</param>
-        ''' <param name="TargetType">The target is a file or directory?</param>
-        ''' <param name="FullSourcePath">Full path to source directory / file.</param>
-        ''' <param name="FullTargetPath">Full path to target directory / file.</param>
-        ''' <param name="ShowUI">Show all dialogs or just the error dialogs.</param>
-        ''' <param name="OnUserCancel">Throw exception or ignore if user cancels the operation.</param>
-        ''' <remarks>
-        ''' Copy/MoveFile will call this directly. Copy/MoveDirectory will call ShellCopyOrMoveDirectory first
-        ''' to change the path if needed.
-        ''' </remarks>
-        Private Shared Sub ShellCopyOrMove(ByVal Operation As CopyOrMove, ByVal TargetType As FileOrDirectory,
-            ByVal FullSourcePath As String, ByVal FullTargetPath As String, ByVal ShowUI As UIOptionInternal, ByVal OnUserCancel As UICancelOption)
-            Debug.Assert(System.Enum.IsDefined(GetType(CopyOrMove), Operation))
-            Debug.Assert(System.Enum.IsDefined(GetType(FileOrDirectory), TargetType))
-            Debug.Assert(FullSourcePath <> "" And IO.Path.IsPathRooted(FullSourcePath), "Invalid FullSourcePath")
-            Debug.Assert(FullTargetPath <> "" And IO.Path.IsPathRooted(FullTargetPath), "Invalid FullTargetPath")
-            Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
-
-            ' Set operation type.
-            Dim OperationType As SHFileOperationType
-            If Operation = CopyOrMove.Copy Then
-                OperationType = SHFileOperationType.FO_COPY
-            Else
-                OperationType = SHFileOperationType.FO_MOVE
-            End If
-
-            ' Set operation details.
-            Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
-
-            ' *** Special action for Directory only. ***
-            Dim FinalSourcePath As String = FullSourcePath
-            If TargetType = FileOrDirectory.Directory Then
-                ' Shell behavior: If target does not exist, create target and copy / move source CONTENT into target.
-                '                 If target exists, copy / move source into target.
-                ' To have our behavior:
-                '   If target does not exist, create target parent (or shell will throw) and call ShellCopyOrMove.
-                '   If target exists, attach "\*" to FullSourcePath and call ShellCopyOrMove.
-                ' In case of Move, since moving the directory, just create the target parent.
-                If IO.Directory.Exists(FullTargetPath) Then
-                    FinalSourcePath = IO.Path.Combine(FullSourcePath, "*")
-                Else
-                    IO.Directory.CreateDirectory(GetParentPath(FullTargetPath))
-                End If
-            End If
-
-            'Call into ShellFileOperation.
-            ShellFileOperation(OperationType, OperationFlags, FinalSourcePath, FullTargetPath, OnUserCancel, TargetType)
-
-            ' *** Special action for Directory only. ***
-            ' In case target does exist, and it's a move, we actually move content and leave the source directory.
-            ' Clean up here.
-            If Operation = CopyOrMove.Move And TargetType = FileOrDirectory.Directory Then
-                If IO.Directory.Exists(FullSourcePath) Then
-                    If IO.Directory.GetDirectories(FullSourcePath).Length = 0 _
-                        AndAlso IO.Directory.GetFiles(FullSourcePath).Length = 0 Then
-                        IO.Directory.Delete(FullSourcePath, recursive:=False)
-                    End If
-                End If
-            End If
-
-        End Sub
-
-        ''' <summary>
-        ''' Sets relevant flags on the SHFILEOPSTRUCT and calls into SHFileOperation to delete file / directory.
-        ''' </summary>
-        ''' <param name="FullPath">Full path to the file / directory.</param>
-        ''' <param name="ShowUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
-        ''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
-        ''' <param name="OnUserCancel">Throw exception or not if the operation was canceled (by user or errors in the system).</param>
-        ''' <remarks>
-        ''' We don't need to consider Recursive flag here since we already verify that in DeleteDirectory.
-        ''' </remarks>
-        Private Shared Sub ShellDelete(ByVal FullPath As String,
-            ByVal ShowUI As UIOptionInternal, ByVal recycle As RecycleOption, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
-
-            Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "FullPath must be a full path")
-            Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
-
-            ' Set fFlags to control the operation details.
-            Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
-            If (recycle = RecycleOption.SendToRecycleBin) Then
-                OperationFlags = OperationFlags Or ShFileOperationFlags.FOF_ALLOWUNDO
-            End If
-
-            ShellFileOperation(SHFileOperationType.FO_DELETE, OperationFlags, FullPath, Nothing, OnUserCancel, FileOrDirectory)
-        End Sub
-
-        ''' <summary>
-        ''' Calls NativeMethods.SHFileOperation with the given SHFILEOPSTRUCT, notifies the shell of change,
-        ''' and throw exceptions if needed.
-        ''' </summary>
-        ''' <param name="OperationType">Value from SHFileOperationType, specifying Copy / Move / Delete</param>
-        ''' <param name="OperationFlags">Value from ShFileOperationFlags, specifying overwrite, recycle bin, etc...</param>
-        ''' <param name="FullSource">The full path to the source.</param>
-        ''' <param name="FullTarget">The full path to the target. Nothing if this is a Delete operation.</param>
-        ''' <param name="OnUserCancel">Value from UICancelOption, specifying to throw or not when user cancels the operation.</param>
-        '''<remarks></remarks>
-        Private Shared Sub ShellFileOperation(ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
-            ByVal FullSource As String, ByVal FullTarget As String, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
-
-            Debug.Assert(System.Enum.IsDefined(GetType(SHFileOperationType), OperationType))
-            Debug.Assert(OperationType <> SHFileOperationType.FO_RENAME, "Don't call Shell to rename")
-            Debug.Assert(FullSource <> "" And IO.Path.IsPathRooted(FullSource), "Invalid FullSource path")
-            Debug.Assert(OperationType = SHFileOperationType.FO_DELETE OrElse (FullTarget <> "" And IO.Path.IsPathRooted(FullTarget)), "Invalid FullTarget path")
-
-
-            ' Get the SHFILEOPSTRUCT
-            Dim OperationInfo As SHFILEOPSTRUCT = GetShellOperationInfo(OperationType, OperationFlags, FullSource, FullTarget)
-
-            Dim Result As Integer
-            Try
-                Result = NativeMethods.SHFileOperation(OperationInfo)
-                ' Notify the shell in case some changes happened.
-                NativeMethods.SHChangeNotify(SHChangeEventTypes.SHCNE_DISKEVENTS,
-                                             SHChangeEventParameterFlags.SHCNF_DWORD, IntPtr.Zero, IntPtr.Zero)
-            Catch
-                Throw
-            Finally
-            End Try
-
-            ' If the operation was canceled, check OnUserCancel and throw OperationCanceledException if needed.
-            ' Otherwise, check the result and throw the appropriate exception if there is an error code.
-            If OperationInfo.fAnyOperationsAborted Then
-                If OnUserCancel = UICancelOption.ThrowException Then
-                    Throw New OperationCanceledException()
-                End If
-            ElseIf Result <> 0 Then
-                ThrowWinIOError(Result)
-            End If
-        End Sub
-        ''' <summary>
-        ''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
-        ''' </summary>
-        ''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
-        ''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
-        ''' <param name="SourcePath">The source file / directory path.</param>
-        ''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
-        ''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
-        Private Shared Function GetShellOperationInfo(
-                            ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
-                            ByVal SourcePath As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
-            Debug.Assert(SourcePath <> "" And IO.Path.IsPathRooted(SourcePath), "Invalid SourcePath")
-
-            Return GetShellOperationInfo(OperationType, OperationFlags, New String() {SourcePath}, TargetPath)
-        End Function
-
-        ''' <summary>
-        ''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
-        ''' </summary>
-        ''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
-        ''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
-        ''' <param name="SourcePaths">A string array containing the paths of source files. Must not be empty.</param>
-        ''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
-        ''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
-        Private Shared Function GetShellOperationInfo(
-                            ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
-                            ByVal SourcePaths() As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
-            Debug.Assert(System.Enum.IsDefined(GetType(SHFileOperationType), OperationType), "Invalid OperationType")
-            Debug.Assert(TargetPath = "" Or IO.Path.IsPathRooted(TargetPath), "Invalid TargetPath")
-            Debug.Assert(SourcePaths IsNot Nothing AndAlso SourcePaths.Length > 0, "Invalid SourcePaths")
-
-            Dim OperationInfo As SHFILEOPSTRUCT
-
-            ' Set wFunc - the operation.
-            OperationInfo.wFunc = CType(OperationType, UInteger)
-
-            ' Set fFlags - the operation details.
-            OperationInfo.fFlags = CType(OperationFlags, UShort)
-
-            ' Set pFrom and pTo - the paths.
-            OperationInfo.pFrom = GetShellPath(SourcePaths)
-            If TargetPath Is Nothing Then
-                OperationInfo.pTo = Nothing
-            Else
-                OperationInfo.pTo = GetShellPath(TargetPath)
-            End If
-
-            ' Set other fields.
-            OperationInfo.hNameMappings = IntPtr.Zero
-            ' Try to set hwnd to the process's MainWindowHandle. If exception occurs, use IntPtr.Zero, which is desktop.
-            Try
-                OperationInfo.hwnd = Process.GetCurrentProcess.MainWindowHandle
-            Catch ex As Exception
-                If TypeOf (ex) Is SecurityException OrElse
-                    TypeOf (ex) Is InvalidOperationException OrElse
-                    TypeOf (ex) Is NotSupportedException Then
-                    ' GetCurrentProcess can throw SecurityException. MainWindowHandle can throw InvalidOperationException or NotSupportedException.
-                    OperationInfo.hwnd = IntPtr.Zero
-                Else
-                    Throw
-                End If
-            End Try
-            OperationInfo.lpszProgressTitle = String.Empty ' We don't set this since we don't have any FOF_SIMPLEPROGRESS.
-
-            Return OperationInfo
-        End Function
-
-        ''' <summary>
-        ''' Return the ShFileOperationFlags based on the ShowUI option.
-        ''' </summary>
-        ''' <param name="ShowUI">UIOptionInternal value.</param>
-        Private Shared Function GetOperationFlags(ByVal ShowUI As UIOptionInternal) As ShFileOperationFlags
-            Dim OperationFlags As ShFileOperationFlags = m_SHELL_OPERATION_FLAGS_BASE
-            If (ShowUI = UIOptionInternal.OnlyErrorDialogs) Then
-                OperationFlags = OperationFlags Or m_SHELL_OPERATION_FLAGS_HIDE_UI
-            End If
-            Return OperationFlags
-        End Function
-
-        ''' <summary>
-        ''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
-        ''' </summary>
-        ''' <param name="FullPath">The full path to be converted.</param>
-        ''' <returns>A string in the required format.</returns>
-        Private Shared Function GetShellPath(ByVal FullPath As String) As String
-            Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "Must be full path")
-
-            Return GetShellPath(New String() {FullPath})
-        End Function
-
-        ''' <summary>
-        ''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
-        ''' </summary>
-        ''' <param name="FullPaths">A string array containing the paths for the operation.</param>
-        ''' <returns>A string in the required format.</returns>
-        Private Shared Function GetShellPath(ByVal FullPaths() As String) As String
-#If DEBUG Then
-            Debug.Assert(FullPaths IsNot Nothing, "FullPaths is NULL")
-            Debug.Assert(FullPaths.Length > 0, "FullPaths() is empty array")
-            For Each FullPath As String In FullPaths
-                Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), FullPath)
-            Next
-#End If
-            ' Each path will end with a Null character.
-            Dim MultiString As New StringBuilder()
-            For Each FullPath As String In FullPaths
-                MultiString.Append(FullPath & ControlChars.NullChar)
-            Next
-            ' Don't need to append another Null character since String always end with Null character by default.
-            Debug.Assert(MultiString.ToString.EndsWith(ControlChars.NullChar, StringComparison.Ordinal))
-
-            Return MultiString.ToString()
-        End Function
-
-
-        ''' <summary>
-        ''' Given an error code from winerror.h, throw the appropriate exception.
-        ''' </summary>
-        ''' <param name="errorCode">An error code from winerror.h.</param>
-        ''' <remarks>
-        ''' - This method is based on sources\ndp\clr\src\BCL\System\IO\_Error.cs::WinIOError, except the following.
-        ''' - Exception message does not contain the path since at this point it is normalized.
-        ''' - Instead of using PInvoke of GetMessage and MakeHRFromErrorCode, use managed code.
-        ''' </remarks>
-        Private Shared Sub ThrowWinIOError(ByVal errorCode As Integer)
-            Select Case errorCode
-                Case NativeTypes.ERROR_FILE_NOT_FOUND
-                    Throw New IO.FileNotFoundException()
-                Case NativeTypes.ERROR_PATH_NOT_FOUND
-                    Throw New IO.DirectoryNotFoundException()
-                Case NativeTypes.ERROR_ACCESS_DENIED
-                    Throw New UnauthorizedAccessException()
-                Case NativeTypes.ERROR_FILENAME_EXCED_RANGE
-                    Throw New IO.PathTooLongException()
-                Case NativeTypes.ERROR_INVALID_DRIVE
-                    Throw New IO.DriveNotFoundException()
-                Case NativeTypes.ERROR_OPERATION_ABORTED, NativeTypes.ERROR_CANCELLED
-                    Throw New OperationCanceledException()
-                Case Else
-                    ' Including these from _Error.cs::WinIOError.
-                    'Case NativeTypes.ERROR_ALREADY_EXISTS
-                    'Case NativeTypes.ERROR_INVALID_PARAMETER
-                    'Case NativeTypes.ERROR_SHARING_VIOLATION
-                    'Case NativeTypes.ERROR_FILE_EXISTS
-                    Throw New IO.IOException((New Win32Exception(errorCode)).Message,
-                        System.Runtime.InteropServices.Marshal.GetHRForLastWin32Error())
-            End Select
-        End Sub
-
-        Private Shared Sub WinNTCopyOrMove(sourceFileFullPath As String, destinationFileFullPath As String)
-            Try
-                Dim succeed As Boolean = NativeMethods.MoveFileEx(
-                        sourceFileFullPath, destinationFileFullPath, m_MOVEFILEEX_FLAGS)
-                ' GetLastWin32Error has to be close to PInvoke call. FxCop rule.
-                If Not succeed Then
-                    ThrowWinIOError(System.Runtime.InteropServices.Marshal.GetLastWin32Error())
-                End If
-            Catch
-                Throw
-            End Try
-        End Sub
-
-        ' Base operation flags used in shell IO operation.
-        ' - DON'T move connected files as a group.
-        ' - DON'T confirm directory creation - our silent copy / move do not.
-        Private Const m_SHELL_OPERATION_FLAGS_BASE As ShFileOperationFlags =
-            ShFileOperationFlags.FOF_NO_CONNECTED_ELEMENTS Or
-            ShFileOperationFlags.FOF_NOCONFIRMMKDIR
-
-        ' Hide UI operation flags for Delete.
-        ' - DON'T show progress bar.
-        ' - DON'T confirm (answer yes to everything). NOTE: In exception cases (read-only file), shell still asks.
-        Private Const m_SHELL_OPERATION_FLAGS_HIDE_UI As ShFileOperationFlags =
-            ShFileOperationFlags.FOF_SILENT Or
-            ShFileOperationFlags.FOF_NOCONFIRMATION
-
-        ' When calling MoveFileEx, set the following flags:
-        ' - Simulate CopyFile and DeleteFile if copied to a different volume.
-        ' - Replace contents of existing target with the contents of source file.
-        ' - Do not return until the file has actually been moved on the disk.
-        Private Const m_MOVEFILEEX_FLAGS As Integer = CInt(
-            MoveFileExFlags.MOVEFILE_COPY_ALLOWED Or
-            MoveFileExFlags.MOVEFILE_REPLACE_EXISTING Or
-            MoveFileExFlags.MOVEFILE_WRITE_THROUGH)
-    End Class
-End Namespace
index b9bdead..daed471 100644 (file)
@@ -14,6 +14,10 @@ Imports System.Security
 Imports System.Text
 
 Imports Microsoft.VisualBasic.CompilerServices
+#If PLATFORM_WINDOWS Then
+Imports Microsoft.VisualBasic.CompilerServices.NativeMethods
+Imports Microsoft.VisualBasic.CompilerServices.NativeTypes
+#End If
 Imports ExUtils = Microsoft.VisualBasic.CompilerServices.ExceptionUtils
 
 Namespace Microsoft.VisualBasic.FileIO
@@ -21,7 +25,7 @@ Namespace Microsoft.VisualBasic.FileIO
     '''  This class represents the file system on a computer. It allows browsing the existing drives, special directories;
     '''  and also contains some commonly use methods for IO tasks.
     ''' </summary>
-    Partial Public Class FileSystem
+    Public Class FileSystem
         ''' <summary>
         ''' Return the names of all available drives on the computer.
         ''' </summary>
@@ -1137,7 +1141,18 @@ Namespace Microsoft.VisualBasic.FileIO
                     ' but have write permission / ACL thus cannot see but can delete / overwrite destination.
 
                     If Environment.OSVersion.Platform = PlatformID.Win32NT Then ' Platforms supporting MoveFileEx.
-                        WinNTCopyOrMove(sourceFileFullPath, destinationFileFullPath)
+#If PLATFORM_WINDOWS Then
+                        Try
+                            Dim succeed As Boolean = NativeMethods.MoveFileEx(
+                                    sourceFileFullPath, destinationFileFullPath, m_MOVEFILEEX_FLAGS)
+                            ' GetLastWin32Error has to be close to PInvoke call. FxCop rule.
+                            If Not succeed Then
+                                ThrowWinIOError(System.Runtime.InteropServices.Marshal.GetLastWin32Error())
+                            End If
+                        Catch
+                            Throw
+                        End Try
+#End If
                     Else ' Non Windows
                         ' IO.File.Delete will not throw if destinationFileFullPath does not exist
                         ' (user may not have permission to discover this, but have permission to overwrite),
@@ -1571,6 +1586,263 @@ Namespace Microsoft.VisualBasic.FileIO
         End Function
 
         ''' <summary>
+        ''' Sets relevant flags on the SHFILEOPSTRUCT and calls SHFileOperation to copy move file / directory.
+        ''' </summary>
+        ''' <param name="Operation">Copy or move.</param>
+        ''' <param name="TargetType">The target is a file or directory?</param>
+        ''' <param name="FullSourcePath">Full path to source directory / file.</param>
+        ''' <param name="FullTargetPath">Full path to target directory / file.</param>
+        ''' <param name="ShowUI">Show all dialogs or just the error dialogs.</param>
+        ''' <param name="OnUserCancel">Throw exception or ignore if user cancels the operation.</param>
+        ''' <remarks>
+        ''' Copy/MoveFile will call this directly. Copy/MoveDirectory will call ShellCopyOrMoveDirectory first
+        ''' to change the path if needed.
+        ''' </remarks>
+        Private Shared Sub ShellCopyOrMove(ByVal Operation As CopyOrMove, ByVal TargetType As FileOrDirectory,
+            ByVal FullSourcePath As String, ByVal FullTargetPath As String, ByVal ShowUI As UIOptionInternal, ByVal OnUserCancel As UICancelOption)
+            Debug.Assert(System.Enum.IsDefined(GetType(CopyOrMove), Operation))
+            Debug.Assert(System.Enum.IsDefined(GetType(FileOrDirectory), TargetType))
+            Debug.Assert(FullSourcePath <> "" And IO.Path.IsPathRooted(FullSourcePath), "Invalid FullSourcePath")
+            Debug.Assert(FullTargetPath <> "" And IO.Path.IsPathRooted(FullTargetPath), "Invalid FullTargetPath")
+            Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
+
+#If PLATFORM_WINDOWS Then
+            ' Set operation type.
+            Dim OperationType As SHFileOperationType
+            If Operation = CopyOrMove.Copy Then
+                OperationType = SHFileOperationType.FO_COPY
+            Else
+                OperationType = SHFileOperationType.FO_MOVE
+            End If
+
+            ' Set operation details.
+            Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
+
+            ' *** Special action for Directory only. ***
+            Dim FinalSourcePath As String = FullSourcePath
+            If TargetType = FileOrDirectory.Directory Then
+                ' Shell behavior: If target does not exist, create target and copy / move source CONTENT into target.
+                '                 If target exists, copy / move source into target.
+                ' To have our behavior:
+                '   If target does not exist, create target parent (or shell will throw) and call ShellCopyOrMove.
+                '   If target exists, attach "\*" to FullSourcePath and call ShellCopyOrMove.
+                ' In case of Move, since moving the directory, just create the target parent.
+                If IO.Directory.Exists(FullTargetPath) Then
+                    FinalSourcePath = IO.Path.Combine(FullSourcePath, "*")
+                Else
+                    IO.Directory.CreateDirectory(GetParentPath(FullTargetPath))
+                End If
+            End If
+
+            ' Call into ShellFileOperation.
+            ShellFileOperation(OperationType, OperationFlags, FinalSourcePath, FullTargetPath, OnUserCancel, TargetType)
+
+            ' *** Special action for Directory only. ***
+            ' In case target does exist, and it's a move, we actually move content and leave the source directory.
+            ' Clean up here.
+            If Operation = CopyOrMove.Move And TargetType = FileOrDirectory.Directory Then
+                If IO.Directory.Exists(FullSourcePath) Then
+                    If IO.Directory.GetDirectories(FullSourcePath).Length = 0 _
+                        AndAlso IO.Directory.GetFiles(FullSourcePath).Length = 0 Then
+                        IO.Directory.Delete(FullSourcePath, recursive:=False)
+                    End If
+                End If
+            End If
+#Else
+            Throw New PlatformNotSupportedException(SR.NoShellCopyOrMove)
+#End If
+
+        End Sub
+
+        ''' <summary>
+        ''' Sets relevant flags on the SHFILEOPSTRUCT and calls into SHFileOperation to delete file / directory.
+        ''' </summary>
+        ''' <param name="FullPath">Full path to the file / directory.</param>
+        ''' <param name="ShowUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
+        ''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
+        ''' <param name="OnUserCancel">Throw exception or not if the operation was canceled (by user or errors in the system).</param>
+        ''' <remarks>
+        ''' We don't need to consider Recursive flag here since we already verify that in DeleteDirectory.
+        ''' </remarks>
+        Private Shared Sub ShellDelete(ByVal FullPath As String,
+            ByVal ShowUI As UIOptionInternal, ByVal recycle As RecycleOption, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
+
+            Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "FullPath must be a full path")
+            Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
+
+#If PLATFORM_WINDOWS Then
+            ' Set fFlags to control the operation details.
+            Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
+            If (recycle = RecycleOption.SendToRecycleBin) Then
+                OperationFlags = OperationFlags Or ShFileOperationFlags.FOF_ALLOWUNDO
+            End If
+
+            ShellFileOperation(SHFileOperationType.FO_DELETE, OperationFlags, FullPath, Nothing, OnUserCancel, FileOrDirectory)
+#Else
+            Throw New PlatformNotSupportedException(SR.NoShellCopyOrMove)
+#End If
+        End Sub
+
+#If PLATFORM_WINDOWS Then
+        ''' <summary>
+        ''' Calls NativeMethods.SHFileOperation with the given SHFILEOPSTRUCT, notifies the shell of change,
+        ''' and throw exceptions if needed.
+        ''' </summary>
+        ''' <param name="OperationType">Value from SHFileOperationType, specifying Copy / Move / Delete</param>
+        ''' <param name="OperationFlags">Value from ShFileOperationFlags, specifying overwrite, recycle bin, etc...</param>
+        ''' <param name="FullSource">The full path to the source.</param>
+        ''' <param name="FullTarget">The full path to the target. Nothing if this is a Delete operation.</param>
+        ''' <param name="OnUserCancel">Value from UICancelOption, specifying to throw or not when user cancels the operation.</param>
+        Private Shared Sub ShellFileOperation(ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
+            ByVal FullSource As String, ByVal FullTarget As String, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
+
+            Debug.Assert(System.Enum.IsDefined(GetType(SHFileOperationType), OperationType))
+            Debug.Assert(OperationType <> SHFileOperationType.FO_RENAME, "Don't call Shell to rename")
+            Debug.Assert(FullSource <> "" And IO.Path.IsPathRooted(FullSource), "Invalid FullSource path")
+            Debug.Assert(OperationType = SHFileOperationType.FO_DELETE OrElse (FullTarget <> "" And IO.Path.IsPathRooted(FullTarget)), "Invalid FullTarget path")
+
+            ' Get the SHFILEOPSTRUCT
+            Dim OperationInfo As SHFILEOPSTRUCT = GetShellOperationInfo(OperationType, OperationFlags, FullSource, FullTarget)
+
+            Dim Result As Integer
+            Try
+                Result = NativeMethods.SHFileOperation(OperationInfo)
+                ' Notify the shell in case some changes happened.
+                NativeMethods.SHChangeNotify(SHChangeEventTypes.SHCNE_DISKEVENTS,
+                                             SHChangeEventParameterFlags.SHCNF_DWORD, IntPtr.Zero, IntPtr.Zero)
+            Catch
+                Throw
+            Finally
+            End Try
+
+            ' If the operation was canceled, check OnUserCancel and throw OperationCanceledException if needed.
+            ' Otherwise, check the result and throw the appropriate exception if there is an error code.
+            If OperationInfo.fAnyOperationsAborted Then
+                If OnUserCancel = UICancelOption.ThrowException Then
+                    Throw New OperationCanceledException()
+                End If
+            ElseIf Result <> 0 Then
+                ThrowWinIOError(Result)
+            End If
+        End Sub
+
+        ''' <summary>
+        ''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
+        ''' </summary>
+        ''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
+        ''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
+        ''' <param name="SourcePath">The source file / directory path.</param>
+        ''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
+        ''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
+        Private Shared Function GetShellOperationInfo(
+                            ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
+                            ByVal SourcePath As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
+            Debug.Assert(SourcePath <> "" And IO.Path.IsPathRooted(SourcePath), "Invalid SourcePath")
+
+            Return GetShellOperationInfo(OperationType, OperationFlags, New String() {SourcePath}, TargetPath)
+        End Function
+
+        ''' <summary>
+        ''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
+        ''' </summary>
+        ''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
+        ''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
+        ''' <param name="SourcePaths">A string array containing the paths of source files. Must not be empty.</param>
+        ''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
+        ''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
+        Private Shared Function GetShellOperationInfo(
+                            ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
+                            ByVal SourcePaths() As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
+            Debug.Assert(System.Enum.IsDefined(GetType(SHFileOperationType), OperationType), "Invalid OperationType")
+            Debug.Assert(TargetPath = "" Or IO.Path.IsPathRooted(TargetPath), "Invalid TargetPath")
+            Debug.Assert(SourcePaths IsNot Nothing AndAlso SourcePaths.Length > 0, "Invalid SourcePaths")
+
+            Dim OperationInfo As SHFILEOPSTRUCT
+
+            ' Set wFunc - the operation.
+            OperationInfo.wFunc = CType(OperationType, UInteger)
+
+            ' Set fFlags - the operation details.
+            OperationInfo.fFlags = CType(OperationFlags, UShort)
+
+            ' Set pFrom and pTo - the paths.
+            OperationInfo.pFrom = GetShellPath(SourcePaths)
+            If TargetPath Is Nothing Then
+                OperationInfo.pTo = Nothing
+            Else
+                OperationInfo.pTo = GetShellPath(TargetPath)
+            End If
+
+            ' Set other fields.
+            OperationInfo.hNameMappings = IntPtr.Zero
+            ' Try to set hwnd to the process's MainWindowHandle. If exception occurs, use IntPtr.Zero, which is desktop.
+            Try
+                OperationInfo.hwnd = Process.GetCurrentProcess.MainWindowHandle
+            Catch ex As Exception
+                If TypeOf (ex) Is SecurityException OrElse
+                    TypeOf (ex) Is InvalidOperationException OrElse
+                    TypeOf (ex) Is NotSupportedException Then
+                    ' GetCurrentProcess can throw SecurityException. MainWindowHandle can throw InvalidOperationException or NotSupportedException.
+                    OperationInfo.hwnd = IntPtr.Zero
+                Else
+                    Throw
+                End If
+            End Try
+            OperationInfo.lpszProgressTitle = String.Empty ' We don't set this since we don't have any FOF_SIMPLEPROGRESS.
+
+            Return OperationInfo
+        End Function
+
+        ''' <summary>
+        ''' Return the ShFileOperationFlags based on the ShowUI option.
+        ''' </summary>
+        ''' <param name="ShowUI">UIOptionInternal value.</param>
+        Private Shared Function GetOperationFlags(ByVal ShowUI As UIOptionInternal) As ShFileOperationFlags
+            Dim OperationFlags As ShFileOperationFlags = m_SHELL_OPERATION_FLAGS_BASE
+            If (ShowUI = UIOptionInternal.OnlyErrorDialogs) Then
+                OperationFlags = OperationFlags Or m_SHELL_OPERATION_FLAGS_HIDE_UI
+            End If
+            Return OperationFlags
+        End Function
+
+        ''' <summary>
+        ''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
+        ''' </summary>
+        ''' <param name="FullPath">The full path to be converted.</param>
+        ''' <returns>A string in the required format.</returns>
+        Private Shared Function GetShellPath(ByVal FullPath As String) As String
+            Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "Must be full path")
+
+            Return GetShellPath(New String() {FullPath})
+        End Function
+
+        ''' <summary>
+        ''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
+        ''' </summary>
+        ''' <param name="FullPaths">A string array containing the paths for the operation.</param>
+        ''' <returns>A string in the required format.</returns>
+        Private Shared Function GetShellPath(ByVal FullPaths() As String) As String
+#If DEBUG Then
+            Debug.Assert(FullPaths IsNot Nothing, "FullPaths is NULL")
+            Debug.Assert(FullPaths.Length > 0, "FullPaths() is empty array")
+            For Each FullPath As String In FullPaths
+                Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), FullPath)
+            Next
+#End If
+
+            ' Each path will end with a Null character.
+            Dim MultiString As New StringBuilder()
+            For Each FullPath As String In FullPaths
+                MultiString.Append(FullPath & ControlChars.NullChar)
+            Next
+            ' Don't need to append another Null character since String always end with Null character by default.
+            Debug.Assert(MultiString.ToString.EndsWith(ControlChars.NullChar, StringComparison.Ordinal))
+
+            Return MultiString.ToString()
+        End Function
+#End If
+
+        ''' <summary>
         ''' Throw an argument exception if the given path starts with "\\.\" (device path).
         ''' </summary>
         ''' <param name="path">The path to check.</param>
@@ -1583,6 +1855,42 @@ Namespace Microsoft.VisualBasic.FileIO
             End If
         End Sub
 
+#If PLATFORM_WINDOWS Then
+        ''' <summary>
+        ''' Given an error code from winerror.h, throw the appropriate exception.
+        ''' </summary>
+        ''' <param name="errorCode">An error code from winerror.h.</param>
+        ''' <remarks>
+        ''' - This method is based on sources\ndp\clr\src\BCL\System\IO\_Error.cs::WinIOError, except the following.
+        ''' - Exception message does not contain the path since at this point it is normalized.
+        ''' - Instead of using PInvoke of GetMessage and MakeHRFromErrorCode, use managed code.
+        ''' </remarks>
+        Private Shared Sub ThrowWinIOError(ByVal errorCode As Integer)
+            Select Case errorCode
+                Case NativeTypes.ERROR_FILE_NOT_FOUND
+                    Throw New IO.FileNotFoundException()
+                Case NativeTypes.ERROR_PATH_NOT_FOUND
+                    Throw New IO.DirectoryNotFoundException()
+                Case NativeTypes.ERROR_ACCESS_DENIED
+                    Throw New UnauthorizedAccessException()
+                Case NativeTypes.ERROR_FILENAME_EXCED_RANGE
+                    Throw New IO.PathTooLongException()
+                Case NativeTypes.ERROR_INVALID_DRIVE
+                    Throw New IO.DriveNotFoundException()
+                Case NativeTypes.ERROR_OPERATION_ABORTED, NativeTypes.ERROR_CANCELLED
+                    Throw New OperationCanceledException()
+                Case Else
+                    ' Including these from _Error.cs::WinIOError.
+                    'Case NativeTypes.ERROR_ALREADY_EXISTS
+                    'Case NativeTypes.ERROR_INVALID_PARAMETER
+                    'Case NativeTypes.ERROR_SHARING_VIOLATION
+                    'Case NativeTypes.ERROR_FILE_EXISTS
+                    Throw New IO.IOException((New Win32Exception(errorCode)).Message,
+                        System.Runtime.InteropServices.Marshal.GetHRForLastWin32Error())
+            End Select
+        End Sub
+#End If
+
         ''' <summary>
         ''' Convert UIOption to UIOptionInternal to use internally.
         ''' </summary>
@@ -1657,6 +1965,31 @@ Namespace Microsoft.VisualBasic.FileIO
             Throw New InvalidEnumArgumentException(argName, argValue, GetType(UICancelOption))
         End Sub
 
+#If PLATFORM_WINDOWS Then
+        ' Base operation flags used in shell IO operation.
+        ' - DON'T move connected files as a group.
+        ' - DON'T confirm directory creation - our silent copy / move do not.
+        Private Const m_SHELL_OPERATION_FLAGS_BASE As ShFileOperationFlags =
+            ShFileOperationFlags.FOF_NO_CONNECTED_ELEMENTS Or
+            ShFileOperationFlags.FOF_NOCONFIRMMKDIR
+
+        ' Hide UI operation flags for Delete.
+        ' - DON'T show progress bar.
+        ' - DON'T confirm (answer yes to everything). NOTE: In exception cases (read-only file), shell still asks.
+        Private Const m_SHELL_OPERATION_FLAGS_HIDE_UI As ShFileOperationFlags =
+            ShFileOperationFlags.FOF_SILENT Or
+            ShFileOperationFlags.FOF_NOCONFIRMATION
+
+        ' When calling MoveFileEx, set the following flags:
+        ' - Simulate CopyFile and DeleteFile if copied to a different volume.
+        ' - Replace contents of existing target with the contents of source file.
+        ' - Do not return until the file has actually been moved on the disk.
+        Private Const m_MOVEFILEEX_FLAGS As Integer = CInt(
+            MoveFileExFlags.MOVEFILE_COPY_ALLOWED Or
+            MoveFileExFlags.MOVEFILE_REPLACE_EXISTING Or
+            MoveFileExFlags.MOVEFILE_WRITE_THROUGH)
+#End If
+
         ' Array containing all the path separator chars. Used to verify that input is a name, not a path.
         Private Shared ReadOnly m_SeparatorChars() As Char = {
             IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar, IO.Path.VolumeSeparatorChar}
index e119aed..47f214d 100644 (file)
@@ -6,7 +6,7 @@ Option Explicit On
 
 Imports System
 Imports System.Environment
-Imports System.Security
+Imports System.Reflection
 Imports Microsoft.VisualBasic.CompilerServices.Utils
 Imports ExUtils = Microsoft.VisualBasic.CompilerServices.ExceptionUtils
 
@@ -122,7 +122,7 @@ Namespace Microsoft.VisualBasic.FileIO
         ''' </remarks>
         Public Shared ReadOnly Property CurrentUserApplicationData() As String
             Get
-                Throw New PlatformNotSupportedException()
+                Return GetDirectoryPath(GetWindowsFormsDirectory("System.Windows.Forms.Application", "UserAppDataPath"), SR.IO_SpecialDirectory_UserAppData)
             End Get
         End Property
 
@@ -139,7 +139,7 @@ Namespace Microsoft.VisualBasic.FileIO
         ''' </remarks>
         Public Shared ReadOnly Property AllUsersApplicationData() As String
             Get
-                Throw New PlatformNotSupportedException()
+                Return GetDirectoryPath(GetWindowsFormsDirectory("System.Windows.Forms.Application", "CommonAppDataPath"), SR.IO_SpecialDirectory_AllUserAppData)
             End Get
         End Property
 
@@ -157,5 +157,14 @@ Namespace Microsoft.VisualBasic.FileIO
             Return FileSystem.NormalizePath(Directory)
         End Function
 
+        Private Shared Function GetWindowsFormsDirectory(typeName As String, propertyName As String) As String
+            Dim type As Type = type.GetType($"{typeName}, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError:=False)
+            Dim [property] As PropertyInfo = type?.GetProperty(propertyName)
+            If [property] Is Nothing Then
+                Return ""
+            End If
+            Return DirectCast([property].GetValue(Nothing, BindingFlags.DoNotWrapExceptions, Nothing, Nothing, Nothing), String)
+        End Function
+
     End Class
 End Namespace
index 62f8929..795b42f 100644 (file)
@@ -238,47 +238,6 @@ Namespace Microsoft.VisualBasic.CompilerServices
         End Enum
 
         ''' <summary>
-        ''' Contains information about the current state of both physical and virtual memory, including extended memory.
-        ''' </summary>
-        <StructLayout(LayoutKind.Sequential)>
-        Friend Structure MEMORYSTATUSEX
-            'typedef struct _MEMORYSTATUSEX {  
-            '   DWORD dwLength;                     Size of the structure. Must set before calling GlobalMemoryStatusEx.
-            '   DWORD dwMemoryLoad;                 Number between 0 and 100 on current memory utilization.
-            '   DWORDLONG ullTotalPhys;             Total size of physical memory.
-            '   DWORDLONG ullAvailPhys;             Total size of available physical memory.
-            '   DWORDLONG ullTotalPageFile;         Size of committed memory limit.
-            '   DWORDLONG ullAvailPageFile;         Size of available memory to committed (ullTotalPageFile max).
-            '   DWORDLONG ullTotalVirtual;          Total size of user potion of virtual address space of calling process.
-            '   DWORDLONG ullAvailVirtual;          Total size of unreserved and uncommitted memory in virtual address space.
-            '   DWORDLONG ullAvailExtendedVirtual;  Total size of unreserved and uncommitted memory in extended portion of virual address.
-            '} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
-
-            Friend dwLength As UInt32
-            Friend dwMemoryLoad As UInt32
-            Friend ullTotalPhys As UInt64
-            Friend ullAvailPhys As UInt64
-            Friend ullTotalPageFile As UInt64
-            Friend ullAvailPageFile As UInt64
-            Friend ullTotalVirtual As UInt64
-            Friend ullAvailVirtual As UInt64
-            Friend ullAvailExtendedVirtual As UInt64
-
-            Friend Sub Init()
-                dwLength = CType(Marshal.SizeOf(GetType(MEMORYSTATUSEX)), UInt32)
-            End Sub
-        End Structure
-
-        ''' <summary>
-        ''' Obtains information about the system's current usage of both physical and virtual memory.
-        ''' </summary>
-        ''' <param name="lpBuffer">Pointer to a MEMORYSTATUSEX structure.</param>
-        ''' <returns>True if the function succeeds. Otherwise, False.</returns>
-        <DllImport("Kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
-        Friend Shared Function GlobalMemoryStatusEx(ByRef lpBuffer As MEMORYSTATUSEX) As <MarshalAsAttribute(UnmanagedType.Bool)> Boolean
-        End Function
-
-        ''' <summary>
         ''' The MoveFileEx function moves an existing file or directory.
         ''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/movefileex.asp
         ''' </summary>
index 9fd680e..0eea73d 100644 (file)
@@ -12,129 +12,6 @@ Namespace Microsoft.VisualBasic.CompilerServices
     <ComponentModel.EditorBrowsableAttribute(ComponentModel.EditorBrowsableState.Never)>
     Friend NotInheritable Class NativeTypes
 
-        <StructLayout(LayoutKind.Sequential)>
-        Friend NotInheritable Class SECURITY_ATTRIBUTES
-            Implements IDisposable
-
-            Friend Sub New()
-                nLength = System.Runtime.InteropServices.Marshal.SizeOf(GetType(SECURITY_ATTRIBUTES))
-            End Sub
-
-            Public nLength As Integer
-            Public lpSecurityDescriptor As IntPtr
-            Public bInheritHandle As Boolean
-
-            Public Overloads Sub Dispose() Implements IDisposable.Dispose
-                If lpSecurityDescriptor <> IntPtr.Zero Then
-                    UnsafeNativeMethods.LocalFree(lpSecurityDescriptor)
-                    lpSecurityDescriptor = IntPtr.Zero
-                End If
-                GC.SuppressFinalize(Me)
-            End Sub
-
-            Protected Overrides Sub Finalize()
-                Dispose()
-                MyBase.Finalize()
-            End Sub
-        End Class
-
-        ''' <summary>
-        ''' Represent Win32 PROCESS_INFORMATION structure. IMPORTANT: Copy the handles to a SafeHandle before use them.
-        ''' </summary>
-        ''' <remarks>
-        ''' The handles in PROCESS_INFORMATION are initialized in unmanaged function.
-        ''' We can't use SafeHandle here because Interop doesn't support [out] SafeHandles in structure / classes yet.
-        ''' This class makes no attempt to free the handles. To use the handle, first copy it to a SafeHandle class
-        ''' (using LateInitSafeHandleZeroOrMinusOneIsInvalid.InitialSetHandle) to correctly use and dispose the handle.
-        ''' </remarks>
-        <StructLayout(LayoutKind.Sequential)>
-        Friend NotInheritable Class PROCESS_INFORMATION
-            Public hProcess As IntPtr = IntPtr.Zero
-            Public hThread As IntPtr = IntPtr.Zero
-            Public dwProcessId As Integer
-            Public dwThreadId As Integer
-
-            Friend Sub New()
-            End Sub
-        End Class
-
-        ''' <summary>
-        ''' Important!  This class should be used where the API being called has allocated the strings.  That is why lpReserved, etc. are declared as IntPtrs instead
-        ''' of Strings - so that the marshaling layer won't release the memory.  This caused us problems in the shell() functions.  We would call GetStartupInfo()
-        ''' which doesn't expect the memory for the strings to be freed.  But because the strings were previously defined as type String, the marshaller would
-        ''' and we got memory corruption problems detectable while running AppVerifier.
-        ''' If you use this structure with an API like CreateProcess() then you are supplying the strings so you'll need another version of this class that defines lpReserved, etc.
-        ''' as String so that the memory will get cleaned up.
-        ''' </summary>
-        ''' <remarks></remarks>
-        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
-        Friend NotInheritable Class STARTUPINFO
-            Implements IDisposable
-
-            Public cb As Integer
-            Public lpReserved As IntPtr = IntPtr.Zero 'not string - see summary
-            Public lpDesktop As IntPtr = IntPtr.Zero 'not string - see summary
-            Public lpTitle As IntPtr = IntPtr.Zero 'not string - see summary
-            Public dwX As Integer
-            Public dwY As Integer
-            Public dwXSize As Integer
-            Public dwYSize As Integer
-            Public dwXCountChars As Integer
-            Public dwYCountChars As Integer
-            Public dwFillAttribute As Integer
-            Public dwFlags As Integer
-            Public wShowWindow As Short
-            Public cbReserved2 As Short
-            Public lpReserved2 As IntPtr = IntPtr.Zero
-            Public hStdInput As IntPtr = IntPtr.Zero
-            Public hStdOutput As IntPtr = IntPtr.Zero
-            Public hStdError As IntPtr = IntPtr.Zero
-
-            Friend Sub New()
-            End Sub
-
-            Private m_HasBeenDisposed As Boolean ' To detect redundant calls. Default initialize = False.
-
-            Protected Overrides Sub Finalize()
-                Dispose(False)
-            End Sub
-
-            ' IDisposable
-            Private Sub Dispose(ByVal disposing As Boolean)
-                If Not m_HasBeenDisposed Then
-                    If disposing Then
-                        m_HasBeenDisposed = True
-
-                        Const STARTF_USESTDHANDLES As Integer = 256 'Defined in windows.h
-                        If (Me.dwFlags And STARTF_USESTDHANDLES) <> 0 Then
-                            If hStdInput <> IntPtr.Zero AndAlso hStdInput <> NativeTypes.INVALID_HANDLE Then
-                                NativeMethods.CloseHandle(hStdInput)
-                                hStdInput = NativeTypes.INVALID_HANDLE
-                            End If
-
-                            If hStdOutput <> IntPtr.Zero AndAlso hStdOutput <> NativeTypes.INVALID_HANDLE Then
-                                NativeMethods.CloseHandle(hStdOutput)
-                                hStdOutput = NativeTypes.INVALID_HANDLE
-                            End If
-
-                            If hStdError <> IntPtr.Zero AndAlso hStdError <> NativeTypes.INVALID_HANDLE Then
-                                NativeMethods.CloseHandle(hStdError)
-                                hStdError = NativeTypes.INVALID_HANDLE
-                            End If
-                        End If 'Me.dwFlags and STARTF_USESTDHANDLES
-
-                    End If
-                End If
-            End Sub
-
-            ' This code correctly implements the disposable pattern.
-            Friend Sub Dispose() Implements IDisposable.Dispose
-                ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
-                Dispose(True)
-                GC.SuppressFinalize(Me)
-            End Sub
-        End Class
-
         <StructLayout(LayoutKind.Sequential)> Friend NotInheritable Class SystemTime
             Public wYear As Short
             Public wMonth As Short
@@ -162,24 +39,6 @@ Namespace Microsoft.VisualBasic.CompilerServices
             MOVEFILE_WRITE_THROUGH = &H8
         End Enum
 
-        ' Handle Values
-        Friend Shared ReadOnly INVALID_HANDLE As IntPtr = New IntPtr(-1)
-
-        ' GetWindow() Constants
-        Friend Const GW_HWNDFIRST As Integer = 0
-        Friend Const GW_HWNDLAST As Integer = 1
-        Friend Const GW_HWNDNEXT As Integer = 2
-        Friend Const GW_HWNDPREV As Integer = 3
-        Friend Const GW_OWNER As Integer = 4
-        Friend Const GW_CHILD As Integer = 5
-        Friend Const GW_MAX As Integer = 5
-
-        'Friend Const EVENTLOG_INFORMATION_TYPE As Integer = 0
-
-        Friend Const STARTF_USESHOWWINDOW As Integer = 1
-
-        Friend Const NORMAL_PRIORITY_CLASS As Integer = &H20
-
         Friend Const LCMAP_TRADITIONAL_CHINESE As Integer = &H4000000I
         Friend Const LCMAP_SIMPLIFIED_CHINESE As Integer = &H2000000I
         Friend Const LCMAP_UPPERCASE As Integer = &H200I
index 5fe3e6d..e831fb1 100644 (file)
@@ -52,6 +52,12 @@ Namespace Microsoft.VisualBasic
 
         End Function
 
+        Public Function Erl() As Integer
+            Dim oProj As ProjectData
+            oProj = ProjectData.GetProjectData()
+            Erl = oProj.m_Err.Erl
+        End Function
+
         Public Function IsArray(ByVal VarName As Object) As Boolean
 
             If VarName Is Nothing Then
index 88767ab..eb4be72 100644 (file)
@@ -3,9 +3,14 @@
 ' See the LICENSE file in the project root for more information.
 
 Imports System
+Imports System.Reflection
 Imports System.Text
 Imports System.Runtime.InteropServices
 
+#If Not PLATFORM_UAP Then
+Imports Microsoft.Win32
+#End If
+
 Imports Microsoft.VisualBasic.CompilerServices
 Imports Microsoft.VisualBasic.CompilerServices.ExceptionUtils
 Imports Microsoft.VisualBasic.CompilerServices.Utils
@@ -13,6 +18,104 @@ Imports Microsoft.VisualBasic.CompilerServices.Utils
 Namespace Microsoft.VisualBasic
 
     Public Module Interaction
+        Private m_SortedEnvList As System.Collections.SortedList
+
+        '============================================================================
+        ' Application/system interaction functions.
+        '============================================================================
+
+        Public Function Shell(ByVal PathName As String, Optional ByVal Style As AppWinStyle = AppWinStyle.MinimizedFocus, Optional ByVal Wait As Boolean = False, Optional ByVal Timeout As Integer = -1) As Integer
+            Return DirectCast(InvokeMethod("Shell", PathName, Style, Wait, Timeout), Integer)
+        End Function
+
+        Public Sub AppActivate(ByVal ProcessId As Integer)
+            InvokeMethod("AppActivateByProcessId", ProcessId)
+        End Sub
+
+        Public Sub AppActivate(ByVal Title As String)
+            InvokeMethod("AppActivateByTitle", Title)
+        End Sub
+
+        Private m_CommandLine As String
+
+        Public Function Command() As String
+
+            If m_CommandLine Is Nothing Then
+                Dim s As String = Environment.CommandLine
+
+                'The first element of the array is the .exe name
+                '  we must remove this when building the return value
+                If (s Is Nothing) OrElse (s.Length = 0) Then
+                    Return ""
+                End If
+
+                'The following code must remove the application name from the command line
+                ' without disturbing the arguments (trailing and embedded spaces)
+                '
+                'We also need to handle embedded spaces in the application name
+                ' as well as skipping over quotations used around embedded spaces within
+                ' the application name
+                '  examples:
+                '       f:\"Program Files"\Microsoft\foo.exe  a b  d   e  f 
+                '       "f:\"Program Files"\Microsoft\foo.exe" a b  d   e  f 
+                '       f:\Program Files\Microsoft\foo.exe                  a b  d   e  f 
+                Dim LengthOfAppName, j As Integer
+
+                'Remove the app name from the arguments
+                LengthOfAppName = Environment.GetCommandLineArgs(0).Length
+
+                Do
+                    j = s.IndexOf(ChrW(34), j)
+                    If j >= 0 AndAlso j <= LengthOfAppName Then
+                        s = s.Remove(j, 1)
+                    End If
+                Loop While (j >= 0 AndAlso j <= LengthOfAppName)
+
+                If j = 0 OrElse j > s.Length Then
+                    m_CommandLine = ""
+                Else
+                    m_CommandLine = LTrim(s.Substring(LengthOfAppName))
+                End If
+            End If
+            Return m_CommandLine
+        End Function
+
+        Public Function Environ(ByVal Expression As Integer) As String
+
+            'Validate index - Note that unlike the fx, this is a legacy VB function and the index is 1 based.
+            If Expression <= 0 OrElse Expression > 255 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_Range1toFF1, "Expression"))
+            End If
+
+            If m_SortedEnvList Is Nothing Then
+                SyncLock m_EnvironSyncObject
+                    If m_SortedEnvList Is Nothing Then
+                        'Constructing the sorted environment list is extremely slow, so we keep a copy around. This list must be alphabetized to match vb5/vb6 behavior
+                        m_SortedEnvList = New System.Collections.SortedList(Environment.GetEnvironmentVariables())
+                    End If
+                End SyncLock
+            End If
+
+            If Expression > m_SortedEnvList.Count Then
+                Return ""
+            End If
+
+            Dim EnvVarName As String = m_SortedEnvList.GetKey(Expression - 1).ToString()
+            Dim EnvVarValue As String = m_SortedEnvList.GetByIndex(Expression - 1).ToString()
+            Return (EnvVarName & "=" & EnvVarValue)
+        End Function
+
+        Private m_EnvironSyncObject As New Object
+
+        Public Function Environ(ByVal Expression As String) As String
+            Expression = Trim(Expression)
+
+            If Expression.Length = 0 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Expression"))
+            End If
+
+            Return Environment.GetEnvironmentVariable(Expression)
+        End Function
 
         '============================================================================
         ' User interaction functions.
@@ -26,6 +129,23 @@ Namespace Microsoft.VisualBasic
 #End If
         End Sub
 
+        Public Function InputBox(ByVal Prompt As String, Optional ByVal Title As String = "", Optional ByVal DefaultResponse As String = "", Optional ByVal XPos As Integer = -1, Optional ByVal YPos As Integer = -1) As String
+            Return DirectCast(InvokeMethod("InputBox", Prompt, Title, DefaultResponse, XPos, YPos), String)
+        End Function
+
+        Public Function MsgBox(ByVal Prompt As Object, Optional ByVal Buttons As MsgBoxStyle = MsgBoxStyle.OkOnly, Optional ByVal Title As Object = Nothing) As MsgBoxResult
+            Return DirectCast(InvokeMethod("MsgBox", Prompt, Buttons, Title), MsgBoxResult)
+        End Function
+
+        Private Function InvokeMethod(methodName As String, ParamArray args As Object()) As Object
+            Dim type As Type = type.GetType("Microsoft.VisualBasic._Interaction, Microsoft.VisualBasic.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError:=False)
+            Dim method As MethodInfo = type?.GetMethod(methodName)
+            If method Is Nothing Then
+                Throw New PlatformNotSupportedException(SR.MethodRequiresSystemWindowsForms)
+            End If
+            Return method.Invoke(Nothing, BindingFlags.DoNotWrapExceptions, Nothing, args, Nothing)
+        End Function
+
         '============================================================================
         ' String functions.
         '============================================================================
@@ -195,6 +315,203 @@ Namespace Microsoft.VisualBasic
             Return Nothing 'If nothing matched above
         End Function
 
+        '============================================================================
+        ' Registry functions.
+        '============================================================================
+
+        Public Sub DeleteSetting(ByVal AppName As String, Optional ByVal Section As String = Nothing, Optional ByVal Key As String = Nothing)
+#If PLATFORM_UAP Then
+            Throw New PlatformNotSupportedException()
+#Else
+            Dim AppSection As String
+            Dim UserKey As RegistryKey
+            Dim AppSectionKey As RegistryKey = Nothing
+
+            CheckPathComponent(AppName)
+            AppSection = FormRegKey(AppName, Section)
+
+            Try
+                UserKey = Registry.CurrentUser
+
+                If IsNothing(Key) OrElse (Key.Length = 0) Then
+                    UserKey.DeleteSubKeyTree(AppSection)
+                Else
+                    AppSectionKey = UserKey.OpenSubKey(AppSection, True)
+                    If AppSectionKey Is Nothing Then
+                        Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Section"))
+                    End If
+
+                    AppSectionKey.DeleteValue(Key)
+                End If
+
+            Catch ex As Exception
+                Throw ex
+            Finally
+                If AppSectionKey IsNot Nothing Then
+                    AppSectionKey.Close()
+                End If
+            End Try
+#End If
+        End Sub
+
+        Public Function GetAllSettings(ByVal AppName As String, ByVal Section As String) As String(,)
+#If PLATFORM_UAP Then
+            Throw New PlatformNotSupportedException()
+#Else
+            Dim rk As RegistryKey
+            Dim sAppSect As String
+            Dim i As Integer
+            Dim lUpperBound As Integer
+            Dim sValueNames() As String
+            Dim sValues(,) As String
+            Dim o As Object
+            Dim sName As String
+
+            ' Check for empty string in path
+            CheckPathComponent(AppName)
+            CheckPathComponent(Section)
+            sAppSect = FormRegKey(AppName, Section)
+            rk = Registry.CurrentUser.OpenSubKey(sAppSect)
+
+
+            If rk Is Nothing Then
+                Return Nothing
+            End If
+
+            GetAllSettings = Nothing
+            Try
+                If rk.ValueCount <> 0 Then
+                    sValueNames = rk.GetValueNames()
+                    lUpperBound = sValueNames.GetUpperBound(0)
+                    ReDim sValues(lUpperBound, 1)
+
+                    For i = 0 To lUpperBound
+                        sName = sValueNames(i)
+
+                        'Assign name
+                        sValues(i, 0) = sName
+
+                        'Assign value
+                        o = rk.GetValue(sName)
+
+                        If (Not o Is Nothing) AndAlso (TypeOf o Is String) Then
+                            sValues(i, 1) = o.ToString()
+                        End If
+                    Next i
+
+                    GetAllSettings = sValues
+                End If
+
+            Catch ex As StackOverflowException
+                Throw ex
+            Catch ex As OutOfMemoryException
+                Throw ex
+            Catch ex As System.Threading.ThreadAbortException
+                Throw ex
+
+            Catch ex As Exception
+                'Consume the exception
+
+            Finally
+                rk.Close()
+            End Try
+#End If
+        End Function
+
+        Public Function GetSetting(ByVal AppName As String, ByVal Section As String, ByVal Key As String, Optional ByVal [Default] As String = "") As String
+#If PLATFORM_UAP Then
+            Throw New PlatformNotSupportedException()
+#Else
+            Dim rk As RegistryKey = Nothing
+            Dim sAppSect As String
+            Dim o As Object
+
+            'Check for empty strings
+            CheckPathComponent(AppName)
+            CheckPathComponent(Section)
+            CheckPathComponent(Key)
+            If [Default] Is Nothing Then
+                [Default] = ""
+            End If
+
+            'Open the sub key
+            sAppSect = FormRegKey(AppName, Section)
+            Try
+                rk = Registry.CurrentUser.OpenSubKey(sAppSect)    'By default, does not request write permission
+
+                'Get the key's value
+                If rk Is Nothing Then
+                    Return [Default]
+                End If
+
+                o = rk.GetValue(Key, [Default])
+            Finally
+                If rk IsNot Nothing Then
+                    rk.Close()
+                End If
+            End Try
+
+            If o Is Nothing Then
+                Return Nothing
+            ElseIf TypeOf o Is String Then ' - odd that this is required to be a string when it isn't in GetAllSettings() above...
+                Return DirectCast(o, String)
+            Else
+                Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue))
+            End If
+#End If
+        End Function
+
+        Public Sub SaveSetting(ByVal AppName As String, ByVal Section As String, ByVal Key As String, ByVal Setting As String)
+#If PLATFORM_UAP Then
+            Throw New PlatformNotSupportedException()
+#Else
+            Dim rk As RegistryKey
+            Dim sIniSect As String
+
+            ' Check for empty string in path
+            CheckPathComponent(AppName)
+            CheckPathComponent(Section)
+            CheckPathComponent(Key)
+
+            sIniSect = FormRegKey(AppName, Section)
+            rk = Registry.CurrentUser.CreateSubKey(sIniSect)
+
+            If rk Is Nothing Then
+                'Subkey could not be created
+                Throw New ArgumentException(GetResourceString(SR.Interaction_ResKeyNotCreated1, sIniSect))
+            End If
+
+            Try
+                rk.SetValue(Key, Setting)
+            Catch ex As Exception
+                Throw ex
+            Finally
+                rk.Close()
+            End Try
+#End If
+        End Sub
+
+        '============================================================================
+        ' Private functions.
+        '============================================================================
+        Private Function FormRegKey(ByVal sApp As String, ByVal sSect As String) As String
+            Const REGISTRY_INI_ROOT As String = "Software\VB and VBA Program Settings"
+            'Forms the string for the key value
+            If IsNothing(sApp) OrElse (sApp.Length = 0) Then
+                FormRegKey = REGISTRY_INI_ROOT
+            ElseIf IsNothing(sSect) OrElse (sSect.Length = 0) Then
+                FormRegKey = REGISTRY_INI_ROOT & "\" & sApp
+            Else
+                FormRegKey = REGISTRY_INI_ROOT & "\" & sApp & "\" & sSect
+            End If
+        End Function
+
+        Private Sub CheckPathComponent(ByVal s As String)
+            If (s Is Nothing) OrElse (s.Length = 0) Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_PathNullOrEmpty))
+            End If
+        End Sub
+
         Public Function CreateObject(ByVal ProgId As String, Optional ByVal ServerName As String = "") As Object
             'Creates local or remote COM2 objects.  Should not be used to create COM+ objects.
             'Applications that need to be STA should set STA either on their Sub Main via STAThreadAttribute
@@ -241,6 +558,43 @@ Namespace Microsoft.VisualBasic
             End Try
         End Function
 
+        Public Function GetObject(Optional ByVal PathName As String = Nothing, Optional ByVal [Class] As String = Nothing) As Object
+            'Only works for Com2 objects, not for COM+ objects.
+
+            If Len([Class]) = 0 Then
+                Try
+                    Return Marshal.BindToMoniker([PathName])
+                Catch ex As StackOverflowException
+                    Throw ex
+                Catch ex As OutOfMemoryException
+                    Throw ex
+                Catch ex As System.Threading.ThreadAbortException
+                    Throw ex
+                Catch
+                    Throw VbMakeException(vbErrors.CantCreateObject)
+                End Try
+            Else
+                If PathName Is Nothing Then
+                    Return Nothing
+                ElseIf Len(PathName) = 0 Then
+                    Try
+                        Dim t As Type = Type.GetTypeFromProgID([Class])
+                        Return System.Activator.CreateInstance(t)
+                    Catch ex As StackOverflowException
+                        Throw ex
+                    Catch ex As OutOfMemoryException
+                        Throw ex
+                    Catch ex As System.Threading.ThreadAbortException
+                        Throw ex
+                    Catch
+                        Throw VbMakeException(vbErrors.CantCreateObject)
+                    End Try
+                Else
+                    Return Nothing
+                End If
+            End If
+        End Function
+
         '============================================================================
         ' Object/latebound functions.
         '============================================================================
index 1c5480a..26d02a1 100644 (file)
   <data name="NoShellDelete" xml:space="preserve">
     <value>UI not available for delete</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="MethodRequiresSystemWindowsForms" xml:space="preserve">
+    <value>Method requires System.Windows.Forms.</value>
+  </data>
+</root>
index 7039fb8..0f863c5 100644 (file)
@@ -13,21 +13,31 @@ namespace Microsoft.VisualBasic.Tests
         [Fact]
         public void Clear()
         {
+            ProjectData.ClearProjectError();
             ProjectData.SetProjectError(new ArgumentException(), 3);
             var errObj = Information.Err();
             errObj.Number = 5;
             errObj.Description = "Description";
+            errObj.HelpContext = 6;
+            errObj.HelpFile = "File";
+            errObj.Source = "Source";
             errObj.Clear();
             Assert.Equal(0, errObj.Erl);
+            Assert.Equal(0, errObj.HelpContext);
+            Assert.Equal("", errObj.HelpFile);
+            Assert.Equal("", errObj.Source);
             Assert.Equal(0, errObj.LastDllError);
             Assert.Equal(0, errObj.Number);
             Assert.Equal("", errObj.Description);
             Assert.Null(errObj.GetException());
+            Assert.Equal(0, Information.Erl());
         }
 
         [Fact]
         public void Raise()
         {
+            ProjectData.ClearProjectError();
+
             ProjectData.SetProjectError(new Exception());
             _ = Assert.Throws<ArgumentException>(() => Information.Err().Raise(0)).ToString();
 
@@ -45,8 +55,29 @@ namespace Microsoft.VisualBasic.Tests
         }
 
         [Fact]
+        public void Source()
+        {
+            ProjectData.ClearProjectError();
+
+            ProjectData.SetProjectError(new Exception() { Source = null });
+            Assert.Null(Information.Err().Source);
+
+            ProjectData.SetProjectError(new Exception() { Source = "MySource1" });
+            Assert.Equal("MySource1", Information.Err().Source);
+
+            ProjectData.SetProjectError(new Exception() { Source = null });
+            Assert.Null(Information.Err().Source);
+
+            ProjectData.SetProjectError(new Exception() { Source = null });
+            _ = Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7, Source: "MySource2"));
+            Assert.Equal("MySource2", Information.Err().Source);
+        }
+
+        [Fact]
         public void FilterDefaultMessage()
         {
+            ProjectData.ClearProjectError();
+
             string message = "Description";
             ProjectData.SetProjectError(new System.IO.FileNotFoundException(message));
             Assert.Equal(message, Information.Err().Description);
@@ -59,5 +90,57 @@ namespace Microsoft.VisualBasic.Tests
             ProjectData.SetProjectError(new System.IO.FileNotFoundException(message));
             Assert.NotEqual(message, Information.Err().Description);
         }
+
+        [Fact]
+        public void MakeHelpLink()
+        {
+            ProjectData.ClearProjectError();
+
+            ProjectData.SetProjectError(new ArgumentException());
+            Assert.Equal("#0", Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7)).HelpLink);
+
+            ProjectData.SetProjectError(new ArgumentException());
+            Assert.Equal("#3", Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7, HelpContext: 3)).HelpLink);
+
+            ProjectData.SetProjectError(new ArgumentException());
+            Assert.Equal("MyFile1#3", Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7, HelpFile: "MyFile1")).HelpLink);
+
+            ProjectData.ClearProjectError();
+            ProjectData.SetProjectError(new ArgumentException());
+            Assert.Equal("MyFile2#0", Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7, HelpFile: "MyFile2")).HelpLink);
+
+            ProjectData.SetProjectError(new ArgumentException());
+            Assert.Equal("MyFile3#3", Assert.Throws<OutOfMemoryException>(() => Information.Err().Raise(7, HelpContext: 3, HelpFile: "MyFile3")).HelpLink);
+        }
+
+        [Theory]
+        [InlineData(null, 0, "")]
+        [InlineData("", 0, "")]
+        [InlineData("#0", 0, "")]
+        [InlineData("#1", 1, "")]
+        [InlineData("#-3", -3, "")]
+        [InlineData("MyFile1", 0, "MyFile1")]
+        [InlineData("MyFile4#4", 4, "MyFile4")]
+        public void ParseHelpLink(string helpLink, int expectedHelpContext, string expectedHelpFile)
+        {
+            ProjectData.ClearProjectError();
+            ProjectData.SetProjectError(new ArgumentException() { HelpLink = helpLink });
+            Assert.Equal(expectedHelpContext, Information.Err().HelpContext);
+            Assert.Equal(expectedHelpFile, Information.Err().HelpFile);
+        }
+
+        [Theory]
+        [InlineData("#")]
+        [InlineData("##")]
+        [InlineData("##2")]
+        [InlineData("MyFile2#")]
+        [InlineData("MyFile3##")]
+        public void ParseHelpLink_InvalidCastException(string helpLink)
+        {
+            ProjectData.ClearProjectError();
+            ProjectData.SetProjectError(new ArgumentException() { HelpLink = helpLink });
+            Assert.Throws<InvalidCastException>(() => Information.Err().HelpContext);
+            Assert.Throws<InvalidCastException>(() => Information.Err().HelpFile);
+        }
     }
 }
index 1d864b7..e98fc89 100644 (file)
@@ -4,12 +4,27 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Xunit;
 
 namespace Microsoft.VisualBasic.Tests
 {
     public class InteractionTests
     {
+        private static readonly bool s_SupportRegistry = !PlatformDetection.IsUap;
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
+        public void AppActivate_ProcessId()
+        {
+            InvokeMissingMethod(() => Interaction.AppActivate(42));
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
+        public void AppActivate_Title()
+        {
+            InvokeMissingMethod(() => Interaction.AppActivate("MyProcess"));
+        }
+
         [Theory]
         [MemberData(nameof(CallByName_TestData))]
         public void CallByName(object instance, string methodName, CallType useCallType, object[] args, Func<object, object> getResult, object expected)
@@ -74,7 +89,6 @@ namespace Microsoft.VisualBasic.Tests
             }
         }
 
-
         [Fact]
         public void Choose()
         {
@@ -91,6 +105,15 @@ namespace Microsoft.VisualBasic.Tests
         }
 
         [Fact]
+        public void Command()
+        {
+            var expected = Environment.CommandLine;
+            var actual = Interaction.Command();
+            Assert.False(string.IsNullOrEmpty(actual));
+            Assert.EndsWith(actual, expected);
+        }
+
+        [Fact]
         public void CreateObject()
         {
             Assert.Throws<NullReferenceException>(() => Interaction.CreateObject(null));
@@ -114,6 +137,125 @@ namespace Microsoft.VisualBasic.Tests
             yield return new object[] { false, 3, "str", "str" };
             yield return new object[] { true, 3, "str", 3 };
         }
+        
+        [Fact]
+        public void DeleteSetting()
+        {
+            if (s_SupportRegistry)
+            {
+                Assert.Throws<ArgumentException>(() => Interaction.DeleteSetting(AppName: "", Section: null, Key: null));
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => Interaction.DeleteSetting(AppName: "", Section: null, Key: null));
+            }
+            // Not tested: valid arguments.
+        }
+
+        [Fact]
+        public void Environ_Index()
+        {
+            var pairs = GetEnvironmentVariables();
+            int n = Math.Min(pairs.Length, 255);
+
+            // Exception.ToString() called to verify message is constructed successfully.
+            _ = Assert.Throws<ArgumentException>(() => Interaction.Environ(0)).ToString();
+            _ = Assert.Throws<ArgumentException>(() => Interaction.Environ(256)).ToString();
+
+            for (int i = 0; i < n; i++)
+            {
+                var pair = pairs[i];
+                Assert.Equal($"{pair.Item1}={pair.Item2}", Interaction.Environ(i + 1));
+            }
+
+            for (int i = n; i < 255; i++)
+            {
+                Assert.Equal("", Interaction.Environ(i + 1));
+            }
+        }
+        
+        [Fact]
+        public void Environ_Name()
+        {
+            var pairs = GetEnvironmentVariables();
+
+            // Exception.ToString() called to verify message is constructed successfully.
+            _ = Assert.Throws<ArgumentException>(() => Interaction.Environ("")).ToString();
+            _ = Assert.Throws<ArgumentException>(() => Interaction.Environ(" ")).ToString();
+
+            foreach (var (key, value) in pairs)
+            {
+                Assert.Equal(value, Interaction.Environ(key));
+            }
+
+            if (pairs.Length > 0)
+            {
+                var (key, value) = pairs[pairs.Length - 1];
+                Assert.Equal(value, Interaction.Environ("  " + key));
+                Assert.Equal(value, Interaction.Environ(key + "  "));
+                Assert.Null(Interaction.Environ(key + "z"));
+            }
+        }
+
+        private static (string, string)[] GetEnvironmentVariables()
+        {
+            var pairs = new List<(string, string)>();
+            var vars = Environment.GetEnvironmentVariables();
+            foreach (var key in vars.Keys) {
+                pairs.Add(((string)key, (string)vars[key]));
+            }
+            return pairs.OrderBy(pair => pair.Item1).ToArray();
+        }
+
+        [Fact]
+        public void GetAllSettings()
+        {
+            if (s_SupportRegistry)
+            {
+                Assert.Throws<ArgumentException>(() => Interaction.GetAllSettings(AppName: "", Section: ""));
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => Interaction.GetAllSettings(AppName: "", Section: ""));
+            }
+            // Not tested: valid arguments.
+        }
+
+        [Fact]
+        public void GetSetting()
+        {
+            if (s_SupportRegistry)
+            {
+                Assert.Throws<ArgumentException>(() => Interaction.GetSetting(AppName: "", Section: "", Key: "", Default: ""));
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => Interaction.GetSetting(AppName: "", Section: "", Key: "", Default: ""));
+            }
+            // Not tested: valid arguments.
+        }
+
+        [Fact]
+        public void GetObject()
+        {
+            Assert.Throws<Exception>(() => Interaction.GetObject());
+            Assert.Throws<Exception>(() => Interaction.GetObject("", null));
+            Assert.Throws<Exception>(() => Interaction.GetObject(null, ""));
+            Assert.Throws<Exception>(() => Interaction.GetObject("", ""));
+            // Not tested: valid arguments.
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
+        public void InputBox()
+        {
+            InvokeMissingMethod(() => Interaction.InputBox("Prompt", Title: "", DefaultResponse: "", XPos: -1, YPos: -1));
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
+        public void MsgBox()
+        {
+            InvokeMissingMethod(() => Interaction.MsgBox("Prompt", Buttons: MsgBoxStyle.ApplicationModal, Title: null));
+        }
 
         [Theory]
         [InlineData(0, 1, 2, 1, " :0")]
@@ -160,6 +302,27 @@ namespace Microsoft.VisualBasic.Tests
         {
             Assert.Throws<OverflowException>(() => Interaction.Partition(Number, Start, Stop, Interval));
         }
+                
+        [Fact]
+        public void SaveSetting()
+        {
+            if (s_SupportRegistry)
+            {
+                Assert.Throws<ArgumentException>(() => Interaction.SaveSetting(AppName: "", Section: "", Key: "", Setting: ""));
+
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => Interaction.SaveSetting(AppName: "", Section: "", Key: "", Setting: ""));
+            }
+            // Not tested: valid arguments.
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
+        public void Shell()
+        {
+            InvokeMissingMethod(() => Interaction.Shell("MyPath", Style: AppWinStyle.NormalFocus, Wait: false, Timeout: -1));
+        }
 
         [Theory]
         [InlineData(null, null)] // empty
@@ -178,5 +341,12 @@ namespace Microsoft.VisualBasic.Tests
         {
             Assert.Throws<ArgumentException>(() => Interaction.Switch(true, "a", false));
         }
+
+        // Methods that rely on reflection of missing assembly.
+        private static void InvokeMissingMethod(Action action)
+        {
+            // Exception.ToString() called to verify message is constructed successfully.
+            _ = Assert.Throws<PlatformNotSupportedException>(action).ToString();
+        }
     }
 }
index 97f299b..60b01a2 100644 (file)
@@ -27,14 +27,14 @@ namespace Microsoft.VisualBasic.FileIO.Tests
         [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Supported on netfx")]
         public static void AllUsersApplicationDataFolderTest()
         {
-            Assert.Throws<PlatformNotSupportedException>(() => SpecialDirectories.AllUsersApplicationData);
+            Assert.Throws<System.IO.DirectoryNotFoundException>(() => SpecialDirectories.AllUsersApplicationData);
         }
 
         [Fact]
         [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Supported on netfx")]
         public static void CurrentUserApplicationDataFolderTest()
         {
-            Assert.Throws<PlatformNotSupportedException>(() => SpecialDirectories.CurrentUserApplicationData);
+            Assert.Throws<System.IO.DirectoryNotFoundException>(() => SpecialDirectories.CurrentUserApplicationData);
         }
 
         [Fact]