diff --git a/docs/building/windows-full.md b/docs/building/windows-full.md index f75060cddb..f9c7e23fcf 100644 --- a/docs/building/windows-full.md +++ b/docs/building/windows-full.md @@ -57,16 +57,39 @@ output will *not* have a runtime identifier in the path. Thus the output location of `powershell.exe` will be `./src/Microsoft.PowerShell.ConsoleHost/bin/Debug/net451/powershell.exe` -While building is easy, running FullCLR version is not as simple as -CoreCLR version. +Build manually +============== + +The build contains the following steps: + +- generating Visual Studio project: `cmake` +- building `powershell.exe` from generated solution: `msbuild + powershell.sln` +- building managed DLLs: `dotnet publish --runtime net451` + + +What I can do with the produced binaries? +========================================= + +Creating a deployable package out of them is **not a supported scenario**. + +The reason why we are building these binaries is +we have components (i.e. workflows) that are not currently available in the CoreClr version. +We want to make sure that CoreClr PowerShell changes don't introduce regressions in FullClr PowerShelll. + +It's possible to run (for test purposes) the dev version of these binaries as follows. + +Running Dev version of FullClr PowerShell +----------------------------------------- + +Running FullCLR version is not as simple as CoreCLR version. If you just run `./powershell.exe`, you will get a `powershell` process, but all the interesting DLLs (such as `System.Management.Automation.dll`) would be loaded from the Global Assembly Cache (GAC), not your output directory. -[@lzybkr](https://github.com/lzybkr) wrote a module to deal with it -and run side-by-side. +Use `Start-DevPowerShell` helper funciton, to workaround it with `$env:DEVPATH` ```powershell Start-DevPowerShell @@ -88,12 +111,3 @@ Start-DevPowerShell -binDir (Split-Path -Parent (Get-PSOutput)) The default for produced `powershell.exe` is x64. You can control it with `Start-PSBuild -FullCLR -NativeHostArch x86` -Build manually -============== - -The build contains the following steps: - -- generating Visual Studio project: `cmake` -- building `powershell.exe` from generated solution: `msbuild - powershell.sln` -- building managed DLLs: `dotnet publish --runtime net451` diff --git a/docs/learning-powerShell/debugging-from-commandline.md b/docs/learning-powerShell/debugging-from-commandline.md index b4568bd39a..bde85777db 100644 --- a/docs/learning-powerShell/debugging-from-commandline.md +++ b/docs/learning-powerShell/debugging-from-commandline.md @@ -1,7 +1,7 @@ Debugging in PowerShell Command-line ===== -As we know we can debug PowerShell code via GUI tools like [VS Code](./using-vscode.md#debugging-with-vs-code) or [ISE](./using-ise.md#debugging-with-ise). In addition we can directly perform debugging within the PowerShell command-line session by using the PowerShell debugger cmdlets. This document demonstrates how to use the cmdlets for the PowerShell command-line debugging. We will cover two topics: set a debug breakpoint on a line of code and on a variable. +As we know, we can debug PowerShell code via GUI tools like [VS Code](./using-vscode.md#debugging-with-vs-code) or [ISE](./using-ise.md#debugging-with-ise). In addition, we can directly perform debugging within the PowerShell command-line session by using the PowerShell debugger cmdlets. This document demonstrates how to use the cmdlets for the PowerShell command-line debugging. We will cover the following topics: setting a debug breakpoint on a line of code and on a variable. Let's use the following code snippet as our sample script. @@ -21,17 +21,17 @@ Write-Host "$result Celsius" ``` - **1. Set a Breakpoint on a Line** + **1. Setting a Breakpoint on a Line** - Open a [PowerShell editor](learning-powershell.md#powershell-editor) -- Save the above code snippet to a file, let's say "test.ps1" +- Save the above code snippet to a file. For example, "test.ps1" - Go to your command-line PowerShell - Clear existing breakpoints if any ```PowerShell PS /home/jen/debug>Get-PSBreakpoint | Remove-PSBreakpoint ``` -- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint, say set it to line 5 +- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint. In this case, we will set it to line 5 ```PowerShell PS /home/jen/debug>Set-PSBreakpoint -Line 5 -Script ./test.ps1 @@ -40,7 +40,7 @@ ID Script Line Command Variable Action -- ------ ---- ------- -------- ------ 0 test.ps1 5 ``` -- Run the script, test.ps1. As we have set a breakpoint, it is expected the program will break into the debugger at the line 5. +- Run the script "test.ps1". As we have set a breakpoint, it is expected the program will break into the debugger at the line 5. ```PowerShell @@ -54,10 +54,10 @@ At /home/jen/debug/test.ps1:5 char:1 [DBG]: PS /home/jen/debug>> ``` -- The PowerShell prompt has been changed to **[DBG]: PS /home/jen/debug>>** as you may noticed. This means - we have entered into the debug mode. To watch the variables like $celsius, simply type $celsius as below. -- To exit from the debugging, type **"q"** -- To get help for the debugging commands, simple type **"?"** +- The PowerShell prompt now has the prefix **[DBG]:** as you may noticed. This means + we have entered into the debug mode. To watch the variables like $celsius, simply type **$celsius** as below. +- To exit from the debugging, type **q** +- To get help for the debugging commands, simply type **?**. The following is an example of debugging output. ```PowerShell [DBG]: PS /home/jen/debug>> $celsius @@ -103,13 +103,13 @@ PS /home/jen/debug> ``` -**2. Set a Breakpoint on a Variable** -- Clear existing breakpoints if any +**2. Setting a Breakpoint on a Variable** +- Clear existing breakpoints if there are any ```PowerShell PS /home/jen/debug>Get-PSBreakpoint | Remove-PSBreakpoint ``` -- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint, say set it to line 5 +- Use **Set-PSBreakpoint** cmdlet to set a debug breakpoint. In this case, we set it to line 5 ```PowerShell @@ -117,10 +117,10 @@ PS /home/jen/debug> ``` -- Run the script, test.ps1. +- Run the script "test.ps1" Once hit the debug breakpoint, we can type **l** to list the source code that debugger is currently executing. As we can see line 3 has an asterisk at the front, meaning that's the line the program is currently executing and broke into the debugger as illustrated below. -- Type **q** to exit from the debugging mode +- Type **q** to exit from the debugging mode. The following is an example of debugging output. ```PowerShell @@ -165,7 +165,7 @@ PS /home/jen/debug> ``` -Now you know the basics of the PowerShell debugging from PowerShell command-line. For further learning read the following articles. +Now you know the basics of the PowerShell debugging from PowerShell command-line. For further learning, read the following articles. More Reading diff --git a/docs/learning-powerShell/powershell-beginners-guide.md b/docs/learning-powerShell/powershell-beginners-guide.md index bf04234068..7d2dd1013f 100644 --- a/docs/learning-powerShell/powershell-beginners-guide.md +++ b/docs/learning-powerShell/powershell-beginners-guide.md @@ -11,11 +11,18 @@ First you need to launch a PowerShell session by following the [Installing Power Getting Familiar with PowerShell Commands --- +In this section, you will learn how to +- create a file, delete a file and change file directory +- find syntax of PowerShell cmdlets +- get help if you needed +- discover what version of PowerShell you are currently using +- exit a PowerShell session +- and more -As mentioned above PowerShell commands is designed to have Verb-Noun structure, for instance Get-Process, Set-Location, Clear-Host, etc. Let’s exercise some of the basic PowerShell commands also known as **cmdlets**. +As mentioned above, PowerShell commands is designed to have Verb-Noun structure, for instance Get-Process, Set-Location, Clear-Host, etc. Let’s exercise some of the basic PowerShell commands, also known as **cmdlets**. -Please note that we will use the PowerShell prompt sign **PS />** in the following examples as it shows on Linux. -It looks like **PS C:\>** on Windows. +Please note that we will use the PowerShell prompt sign **PS />** as it appears on Linux in the following examples. +It is shown as **PS C:\\>** on Windows. **1. Get-Process**: displays the processes running on your system. @@ -88,7 +95,7 @@ PS /> cls PS /> Set-Location /home PS /home> ``` -**5. ls or dir - Get-ChildItem**: list all items in the specified location +**5. dir - Get-ChildItem**: list all items in the specified location ```PowerShell Get all files under the current directory: @@ -119,10 +126,10 @@ Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 7/7/2016 7:17 PM 0 test.ps1 ``` -You can use the **-value** parameter to add some data to your file. For example, the following command adds the phrase "Write-Host 'Hello There'" as a file content to the test.ps1. Because the test.txt file exists already, we use **-force** parameter to replace the existing content. +You can use the **-Value** parameter to add some data to your file. For example, the following command adds the phrase "Write-Host 'Hello There'" as a file content to the test.ps1. Because the test.ps1 file exists already, we use **-Force** parameter to replace the existing content. ```PowerShell -PS /home/jen> New-Item -Path ./test.ps1 -Value "Write-Host 'hello there'" -force +PS /home/jen> New-Item -Path ./test.ps1 -Value "Write-Host 'hello there'" -Force Directory: /home/jen @@ -132,7 +139,7 @@ Mode LastWriteTime Length Name -a---- 7/7/2016 7:19 PM 24 test.ps1 ``` -There are other ways to add some data to a file, for example, you can use Set-Content to set the file contents: +There are other ways to add some data to a file. For example, you can use Set-Content to set the file contents: ```PowerShell PS /home/jen>Set-Content -Path ./test.ps1 -Value "Write-Host 'hello there again!'" @@ -162,19 +169,34 @@ This cmdlet will delete the file /home/jen/test.ps1: ```PowerShell PS /home/jen> Remove-Item ./test.ps1 ``` -**9. Exit**: - to exit the PowerShell session, type "exit" + +**9. $PSVersionTable**: displays the version of PowerShell you are currently using + +Type **$PSVersionTable** in your PowerShell session, you will see something like below. "PSVersion" indicates the +PowerShell version that you are using. + +```PowerShell +Name Value +---- ----- +PSVersion 5.1.10032.0 +PSEdition Linux +PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} +BuildVersion 3.0.0.0 +CLRVersion +WSManStackVersion 1.0 +PSRemotingProtocolVersion 2.3 +SerializationVersion 1.1.0.1 + +``` + +**10. Exit**: to exit the PowerShell session, type "exit" ```PowerShell PS /home/jen> exit ``` Need Help? ---- -The most important command in PowerShell is possibly the Get-Help, which allows you to quickly learn PowerShell without having to surfing around the Internet. The Get-Help cmdlet also shows you how PowerShell commands work with examples. - - -PS />**Get-Help** - -You can use this cmdlet to get help with any PowerShell commands. +The most important command in PowerShell is possibly the Get-Help, which allows you to quickly learn PowerShell without having to surf around the Internet. The Get-Help cmdlet also shows you how PowerShell commands work with examples. PS />**Get-Help -Name Get-Process** @@ -184,14 +206,14 @@ It shows the syntax and other technical information of the Get-Process cmdlet. PS />**Get-Help -Name Get-Process -Examples** It displays the examples how to use the Get-Process cmdlet. -If you use **-full** parameter, i.e., "Get-Help -Name Get-Process -Full", it will display more technical information. +If you use **-Full** parameter, i.e., "Get-Help -Name Get-Process -Full", it will display more technical information. Discover All Commands Available on Your System ---- -You want to discover what PowerShell cmdlets available on your system. Simple, just run "Get-Command" as below. +You want to discover what PowerShell cmdlets available on your system? Simple, just run "Get-Command" as below. PS /> **Get-Command** @@ -203,14 +225,14 @@ If you want to know the syntax of Get-Process cmdlet, type PS /> **Get-Command Get-Process -Syntax** -If you want to know how to sue the get-process, type +If you want to know how to use the Get-Process, type -PS /> **Get-Help Get-Process -example** +PS /> **Get-Help Get-Process -Example** PowerShell Pipeline '|' ---- -Sometimes when you run Get-ChildItem or "dir", you want to get a list of files in a descending order. To archive that, type: +Sometimes when you run Get-ChildItem or "dir", you want to get a list of files in a descending order. To achieve that, type: ```PowerShell PS /home/jen> dir | sort -Descending ``` @@ -229,10 +251,10 @@ Mode LastWriteTime Length Name ``` How to Create and Run PowerShell scripts ---- -- You can use ISE, VS Code, or any favorite editor to create a PowerShell script and save the script with a .ps1 file extension (helloworld.ps1 in the example) -- To run the script, cd to your current folder and type ./helloworld.ps1 +- You can use ISE, VS Code or your favorite editor to create a PowerShell script and save the script with a .ps1 file extension (for example, helloworld.ps1) +- To run the script, cd to your current folder and type ./yourscript.ps1 (for example, ./helloworld.ps1). -See [Running PowerShell Scripts Is as Easy as 1-2-3] [run-ps] for more details. +Note: if you are using Windows, make sure you set the PowerShell's execution policy to "RemoteSigned" in this case. See [Running PowerShell Scripts Is as Easy as 1-2-3] [run-ps] for more details. [run-ps]:http://windowsitpro.com/powershell/running-powershell-scripts-easy-1-2-3 diff --git a/docs/learning-powerShell/using-ise.md b/docs/learning-powerShell/using-ise.md index 59724b2786..06f34504d1 100644 --- a/docs/learning-powerShell/using-ise.md +++ b/docs/learning-powerShell/using-ise.md @@ -1,14 +1,14 @@ Using PowerShell Integrated Scripting Environment (ISE) ==== -The PowerShell ISE works on Windows. If you are not using Windows, please see [Using VS Code](./using-vscode.md). +The PowerShell ISE only works on Windows. If you are not using Windows, please see [Using VS Code](./using-vscode.md). Editing with ISE --- - Launch PowerShell ISE - * Press Windows Key -> type PowerShell ISE, click on PowerShell ISE to launch the ISE + * Press Windows Key -> type "PowerShell ISE", click on PowerShell ISE to launch the ISE - Create a new PowerShell Script - * Click on File -> New - * Add a few lines of PowerShell scripts in your newly created file, for example, + * Click on **File->New** + * Add a few lines of PowerShell code in your newly created file. In this case, we will use the following script snippet. ```PowerShell # Convert Fahrenheit to Celsius @@ -25,21 +25,21 @@ Write-Host "$result Celsius" ``` * **Note**: You can find more examples [here](http://examples.oreilly.com/9780596528492/). -- Save the script file - * Click on File-> Save As -> type "helloworld.ps1" -- Close the helloworld.ps1 - * File-> Close -- Reopen the helloworld.ps1 - * File-> Open, then choose helloworld.ps1. +- To save the script file + * Click on **File->Save As**. Then type "helloworld.ps1" +- To close the helloworld.ps1 file + * **File->Close** +- To reopen the helloworld.ps1 file + * **File->Open**, then choose helloworld.ps1 - For more details, go to [How to Write and Run Scripts in the Windows PowerShell ISE](https://msdn.microsoft.com/en-us/powershell/scripting/core-powershell/ise/how-to-write-and-run-scripts-in-the-windows-powershell-ise). Debugging with ISE ---- -To execute the entire script file, you can press **F5**; to execute several lines of your scripts, simply select them and press **F8**. However sometimes you would like to stop the execution on a particular line in order to exam some variables to check if the program runs as expected. In that case, you may follow the steps below. Let's take the helloworld.ps1 as an example and assume line 17 is the place where you want to stop. +To execute the entire script file, you can press **F5**. To execute several lines of your scripts, simply select them and press **F8**. If you would like to stop the execution on a particular line in order to examine some variables to check if the program runs as expected. In that case, you may follow the steps below. Let's take the helloworld.ps1 as an example and assume line 17 is the place where you want to stop. -- Set a break point: Move mouse over on the line 17, and press **F9**. You will see the line 17 is highlighted which means a breakpoint gets set. +- Set a break point: Move mouse over on the line 17, and press **F9**. You will see the line 17 is highlighted, which means a breakpoint is set. - Press **F5** to run the script - Enter 80 (or any number in Fahrenheit) from the command line prompt - Notice that the ISE output pane becomes “[DBG]: PS C:\Test>>” prompt. This means the program is in the debugging mode. It stops at Line 17: @@ -52,7 +52,7 @@ Hit Line breakpoint on 'C:\test\helloword.ps1:17' ``` -- From the output pane, you can type $celsius and $fahrenheit to exam the values of these variables to see if they are correct. +- From the output pane, you can type $celsius and $fahrenheit to examine the values of these variables to see if they are correct. ```PowerShell [DBG]: PS C:\Test>> $celsius diff --git a/docs/learning-powerShell/using-vscode.md b/docs/learning-powerShell/using-vscode.md index ebe677f595..38a7d3dc4c 100644 --- a/docs/learning-powerShell/using-vscode.md +++ b/docs/learning-powerShell/using-vscode.md @@ -1,11 +1,11 @@ Using Visual Studio Code (VS Code) ==== -If you are working on Linux and OS X, you cannot use ISE because it is not supported on these platforms. In this case you can choose your favorite editor to write PowerShell scripts. Here we choose VS Code as a PowerShell editor as an example. +If you are working on Linux and OS X, you cannot use ISE because it is not supported on these platforms. In this case, you can choose your favorite editor to write PowerShell scripts. Here we choose VS Code as a PowerShell editor as an example. -You can use VS Code on Windows with PowerShell V5 by using Windows 10 or by installing [Windows Management Framework 5.0 RTM](https://www.microsoft.com/en-us/download/details.aspx?id=50395) for down-level Windows OSs. +You can use VS Code on Windows with PowerShell V5 by using Windows 10 or by installing [Windows Management Framework 5.0 RTM](https://www.microsoft.com/en-us/download/details.aspx?id=50395) for down-level Windows OSs (e.g. Windows 8.1, etc.). -Before starting it, please make sure PowerShell exists on your system. Follow the [Installing PowerShell](./learning-powershell.md#installing-powershell) instruction you can install the PowerShell and launch the PowerShell session. +Before starting it, please make sure PowerShell exists on your system. By following the [Installing PowerShell](./learning-powershell.md#installing-powershell) instructions you can install PowerShell and launch a PowerShell session. Editing with VS Code ---- @@ -28,28 +28,28 @@ Editing with VS Code - Press **F1** (or **Ctrl+Shift+P**) which opens up the “Command Palette” inside the VS Code app. -- In the command palette, type **ext install** and hit Enter. It will show all VS Code extensions available on your system. -- Choose PowerShell and click on Install, you will see something like below +- In the command palette, type **ext install** and hit **Enter**. It will show all VS Code extensions available on your system. +- Choose PowerShell and click on **Install**, you will see something like below ![VSCode](vscode.png) - After the install, you will see the **Install** button turns to **Enable**. -- Click on Enable and OK -- Now you are ready for editing. for example, to create a new file, click File->New; to save it, click File->Save and then provide - a file name, let's say "helloworld.ps1"; to close the file, click on "x"; to exit the VS Code, File->Exit. +- Click on **Enable** and **OK** +- Now you are ready for editing. For example, to create a new file, click **File->New**. To save it, click **File->Save** and then provide + a file name, let's say "helloworld.ps1". To close the file, click on "x" next to the file name. To exit VS Code, **File->Exit**. Debugging with VS Code ---- -- Open a file folder (**File->Open Folder**) where contains the PowerShell modules or scripts you have written already and want to debug. In this example, we saved helloworld.ps1 under home/jen/debug. Thus we select the "debug" folder and open it in VS Code. +- Open a file folder (**File->Open Folder**) that contains the PowerShell modules or scripts you have written already and want to debug. In this example, we saved the helloworld.ps1 under a directory called "demo". Thus we select the "demo" folder and open it in VS Code. - Creating the Debug Configuration (launch.json) - Because some information regarding your scripts is needed for debugger to start executing your script, we need to set up the debug config First. This is one time only to debug PowerShell scripts under your current folder. + Because some information regarding your scripts is needed for debugger to start executing your script, we need to set up the debug config first. This is one-time process to debug PowerShell scripts under your current folder. In our case, the "demo" folder. * Click on the **Debug** icon (or **Ctrl+Shift+D**) - * Click on the **Settings** icon that looks like a gear. The VS Code will prompt you to **Select Environment**. Choose **PowerShell**. Then the VS code will auto create a debug configuration settings file in the same folder. It looks like the following: + * Click on the **Settings** icon that looks like a gear. VS Code will prompt you to **Select Environment**. Choose **PowerShell**. Then VS code will auto create a debug configuration settings file in the same folder. It looks like the following: ```json { "version": "0.2.0", @@ -73,9 +73,10 @@ Debugging with VS Code ] } ``` -- Once the debug configuration is established, now go to your helloworld.ps1 and set a breakpoint by pressing **F9** on a line you wish to debug break into. +- Once the debug configuration is established, go to your helloworld.ps1 and set a breakpoint by pressing **F9** on a line you wish to debug. - To disable the breakpoint, press **F9** again. -- Press **F5** to let the run. +- Press **F5** to run the script. The execution should stop on the line you put the breakpoint on. +- Press **F5** to continue running the script. There are a few blogs that may be helpful to get you started using PowerShell extension for VS Code diff --git a/src/Microsoft.PowerShell.ConsoleHost/project.json b/src/Microsoft.PowerShell.ConsoleHost/project.json index bc4ae348b6..f971813c8a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/project.json +++ b/src/Microsoft.PowerShell.ConsoleHost/project.json @@ -61,7 +61,8 @@ "Microsoft.PowerShell.Commands.Management": "1.0.0-*", "Microsoft.PowerShell.Commands.Utility": "1.0.0-*", "Microsoft.PowerShell.LocalAccounts": "1.0.0-*", - "Microsoft.PowerShell.PackageManagement": "1.0.0-*" + "Microsoft.PowerShell.PackageManagement": "1.0.0-*", + "Microsoft.Management.Infrastructure.CimCmdlets": "1.0.0-*" }, "frameworks": { diff --git a/src/Microsoft.PowerShell.PSReadLine/Cmdlets.cs b/src/Microsoft.PowerShell.PSReadLine/Cmdlets.cs index 514eed8ed7..fbd0c867c5 100644 --- a/src/Microsoft.PowerShell.PSReadLine/Cmdlets.cs +++ b/src/Microsoft.PowerShell.PSReadLine/Cmdlets.cs @@ -92,7 +92,12 @@ namespace Microsoft.PowerShell public const ConsoleColor DefaultEmphasisForegroundColor = ConsoleColor.Cyan; public const ConsoleColor DefaultErrorForegroundColor = ConsoleColor.Red; - public const EditMode DefaultEditMode = EditMode.Windows; + public const EditMode DefaultEditMode = +#if LINUX + EditMode.Emacs; +#else + EditMode.Windows; +#endif public const string DefaultContinuationPrompt = ">> "; diff --git a/src/Microsoft.PowerShell.PSReadLine/KeyBindings.cs b/src/Microsoft.PowerShell.PSReadLine/KeyBindings.cs index aae97e503e..b543aef8a1 100644 --- a/src/Microsoft.PowerShell.PSReadLine/KeyBindings.cs +++ b/src/Microsoft.PowerShell.PSReadLine/KeyBindings.cs @@ -114,6 +114,25 @@ namespace Microsoft.PowerShell private Dictionary _dispatchTable; private Dictionary> _chordDispatchTable; + /// + /// Helper to set bindings based on EditMode + /// + void SetDefaultBindings(EditMode editMode) + { + switch (editMode) + { + case EditMode.Emacs: + SetDefaultEmacsBindings(); + break; + case EditMode.Vi: + SetDefaultViBindings(); + break; + case EditMode.Windows: + SetDefaultWindowsBindings(); + break; + } + } + void SetDefaultWindowsBindings() { _dispatchTable = new Dictionary(new ConsoleKeyInfoComparer()) diff --git a/src/Microsoft.PowerShell.PSReadLine/Options.cs b/src/Microsoft.PowerShell.PSReadLine/Options.cs index d8162b743f..b927282e27 100644 --- a/src/Microsoft.PowerShell.PSReadLine/Options.cs +++ b/src/Microsoft.PowerShell.PSReadLine/Options.cs @@ -96,18 +96,7 @@ namespace Microsoft.PowerShell // Switching/resetting modes - clear out chord dispatch table _chordDispatchTable.Clear(); - switch (options._editMode) - { - case EditMode.Emacs: - SetDefaultEmacsBindings(); - break; - case EditMode.Vi: - SetDefaultViBindings(); - break; - case EditMode.Windows: - SetDefaultWindowsBindings(); - break; - } + SetDefaultBindings(Options.EditMode); } if (options._showToolTips.HasValue) { diff --git a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs b/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs index c2848f0546..75654e21d1 100644 --- a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs +++ b/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs @@ -485,8 +485,6 @@ namespace Microsoft.PowerShell _console = new ConhostConsole(); #endif - SetDefaultWindowsBindings(); - _buffer = new StringBuilder(8 * 1024); _statusBuffer = new StringBuilder(256); _savedCurrentLine = new HistoryItem(); @@ -516,6 +514,7 @@ namespace Microsoft.PowerShell hostName = "PSReadline"; } _options = new PSConsoleReadlineOptions(hostName); + SetDefaultBindings(_options.EditMode); } private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) diff --git a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs index 5674327cdd..59a864d01c 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsExtensions.cs @@ -893,13 +893,10 @@ namespace System.Management.Automation return GetEnvironmentVariables(); } - if (!Platform.HasRegistrySupport()) - { - // Porting note: Does not throw a PlatformUnsupported because - // GetEnvironmentVariable cannot throw, and we want the same interface. - return null; - } - +#if LINUX + return null; +#else + if( target == EnvironmentVariableTarget.Machine) { using (RegistryKey environmentKey = @@ -916,6 +913,7 @@ namespace System.Management.Automation return GetRegistryKeyNameValuePairs(environmentKey); } } +#endif } /// @@ -956,13 +954,9 @@ namespace System.Management.Automation return System.Environment.GetEnvironmentVariable(variable); } - if (!Platform.HasRegistrySupport()) - { - // Porting note: This cannot throw since it would otherwise throw while - // creating a runspace. Returning null is appropriate to signal that there - // is no value for a registry key with no registry. - return null; - } +#if LINUX + return null; +#else if (target == EnvironmentVariableTarget.Machine) { @@ -985,6 +979,7 @@ namespace System.Management.Automation return value; } } +#endif } #endregion EnvironmentVariable_Extensions diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index eb9d3b2624..e4a8701b6e 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -143,12 +143,12 @@ namespace System.Management.Automation //the user has set XDG_DATA_HOME corresponding to module path if (String.IsNullOrEmpty(xdgdatahome)){ - // create the xdg folder if needed - if (!Directory.Exists(xdgDataHomeDefault)) - { - Directory.CreateDirectory(xdgDataHomeDefault); - } - return xdgDataHomeDefault; + // create the xdg folder if needed + if (!Directory.Exists(xdgDataHomeDefault)) + { + Directory.CreateDirectory(xdgDataHomeDefault); + } + return xdgDataHomeDefault; } else { @@ -159,12 +159,12 @@ namespace System.Management.Automation //the user has set XDG_DATA_HOME corresponding to module path if (String.IsNullOrEmpty(xdgdatahome)){ - //xdg values have not been set - if (!Directory.Exists(xdgModuleDefault)) //module folder not always guaranteed to exist - { - Directory.CreateDirectory(xdgModuleDefault); - } - return xdgModuleDefault; + //xdg values have not been set + if (!Directory.Exists(xdgModuleDefault)) //module folder not always guaranteed to exist + { + Directory.CreateDirectory(xdgModuleDefault); + } + return xdgModuleDefault; } else { @@ -175,7 +175,7 @@ namespace System.Management.Automation //the user has set XDG_CACHE_HOME if (String.IsNullOrEmpty(xdgcachehome)) { - //xdg values have not been set + //xdg values have not been set if (!Directory.Exists(xdgCacheDefault)) //module folder not always guaranteed to exist { Directory.CreateDirectory(xdgCacheDefault); @@ -217,90 +217,6 @@ namespace System.Management.Automation } - // ComObjectType is null on CoreCLR for Linux since there is - // no COM support on Linux - internal static bool HasCom() - { - // TODO: catch exception from Type.IsComObject - return IsWindows; - } - - // The Antimalware Scan Interface is not supported on Linux - internal static bool HasAmsi() - { - return IsWindows; - } - - // This is mainly with respect to the auto-mounting of - // disconnected network drives on Windows - internal static bool HasDriveAutoMounting() - { - return IsWindows; - } - - // Linux does not have a registry - internal static bool HasRegistrySupport() - { - return IsWindows; - } - - // Linux does not have PowerShell execution policies - internal static bool HasExecutionPolicy() - { - return IsWindows; - } - - // Linux has a single rooted file system - internal static bool HasSingleRootFilesystem() - { - return !IsWindows; - } - - // Linux has no notion of file shares. It has mount points - // instead, which are subdirectories of its single-root - // filesystem. - internal static bool HasFileShares() - { - return !HasSingleRootFilesystem(); - } - - // Linux has no support for UNC, just mounts in a single rooted hierarchy - // the UNC equivalent of a "network drive" aka mount would be the mount itself - internal static bool HasUNCSupport() - { - return IsWindows; - } - - // Linux uses .net to query file attributes - internal static bool UseDotNetToQueryFileAttributes() - { - return true; - } - - // Linux does not have group policy support - internal static bool HasGroupPolicySupport() - { - return IsWindows; - } - - // non-windows does not have network drive support - internal static bool HasNetworkDriveSupport() - { - return IsWindows; - } - - // non-windows does not have reparse points - internal static bool SupportsReparsePoints() - { - return IsWindows; - } - - // non-windows does not support removing drives - internal static bool SupportsRemoveDrive() - { - return IsWindows; - } - // Platform methods prefixed NonWindows are: // - non-windows by the definition of the IsWindows method above // - here, because porting to Linux and other operating systems @@ -313,27 +229,17 @@ namespace System.Management.Automation internal static bool NonWindowsIsHardLink(ref IntPtr handle) { - return LinuxPlatform.IsHardLink(ref handle); + return Unix.IsHardLink(ref handle); } internal static bool NonWindowsIsHardLink(FileSystemInfo fileInfo) { - if (!IsWindows) - { - return LinuxPlatform.IsHardLink(fileInfo); - } - - throw new PlatformNotSupportedException(); + return Unix.IsHardLink(fileInfo); } internal static bool NonWindowsIsSymLink(FileSystemInfo fileInfo) { - if (!IsWindows) - { - return LinuxPlatform.IsSymLink(fileInfo); - } - - throw new PlatformNotSupportedException(); + return Unix.NativeMethods.IsSymLink(fileInfo.FullName); } internal static string NonWindowsInternalGetTarget(SafeFileHandle handle) @@ -344,166 +250,101 @@ namespace System.Management.Automation internal static string NonWindowsInternalGetTarget(string path) { - if (!IsWindows) - { - return LinuxPlatform.FollowSymLink(path); - } - else - { - throw new PlatformNotSupportedException(); - } + return Unix.NativeMethods.FollowSymLink(path); } internal static string NonWindowsGetUserFromPid(int path) { - if (!IsWindows) - { - return LinuxPlatform.GetUserFromPid(path); - } - else - { - throw new PlatformNotSupportedException(); - } + return Unix.NativeMethods.GetUserFromPid(path); } -#if CORECLR + #if CORECLR internal static string NonWindowsGetFolderPath(SpecialFolder folder) { - if (!IsWindows) - { - return LinuxPlatform.GetFolderPath(folder); - } - else - { - throw new PlatformNotSupportedException(); - } + return Unix.GetFolderPath(folder); } -#endif + #endif internal static string NonWindowsInternalGetLinkType(FileSystemInfo fileInfo) { - if (!IsWindows) + if (NonWindowsIsSymLink(fileInfo)) { - if (NonWindowsIsSymLink(fileInfo)) - { - return "SymbolicLink"; - } - if (NonWindowsIsHardLink(fileInfo)) - { - return "HardLink"; - } - return null; - } - else - { - throw new PlatformNotSupportedException(); + return "SymbolicLink"; } + if (NonWindowsIsHardLink(fileInfo)) + { + return "HardLink"; + } + + return null; } - internal static bool NonWindowsCreateSymbolicLink(string path, string strTargetPath, bool isDirectory) + internal static bool NonWindowsCreateSymbolicLink(string path, string target) { - if (!IsWindows) - { - // Linux doesn't care if target is a directory or not - return LinuxPlatform.CreateSymbolicLink(path, strTargetPath); - } - else - { - throw new PlatformNotSupportedException(); - } + // Linux doesn't care if target is a directory or not + return Unix.NativeMethods.CreateSymLink(path, target); } internal static bool NonWindowsCreateHardLink(string path, string strTargetPath) { - if (!IsWindows) - { - return LinuxPlatform.CreateHardLink(path, strTargetPath); - } - else - { - throw new PlatformNotSupportedException(); - } + return Unix.CreateHardLink(path, strTargetPath); } internal static void NonWindowsSetDate(DateTime dateToUse) { - if (!IsWindows) - { - LinuxPlatform.SetDateInfoInternal date = new LinuxPlatform.SetDateInfoInternal(dateToUse); - LinuxPlatform.SetDate(date); - } - else - { - throw new PlatformNotSupportedException(); - } + Unix.SetDateInfoInternal date = new Unix.SetDateInfoInternal(dateToUse); + Unix.SetDate(date); } internal static string NonWindowsGetDomainName() { - if (!IsWindows) + string fullyQualifiedName = Unix.NativeMethods.GetFullyQualifiedName(); + if (string.IsNullOrEmpty(fullyQualifiedName)) { - string fullyQualifiedName = LinuxPlatform.Native.GetFullyQualifiedName(); - if (string.IsNullOrEmpty(fullyQualifiedName)) - { - int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.NonWindowsGetDomainName error: " + lastError); - } - - int index = fullyQualifiedName.IndexOf('.'); - if (index >= 0) - { - return fullyQualifiedName.Substring(index + 1); - } - - return ""; + int lastError = Marshal.GetLastWin32Error(); + throw new InvalidOperationException("Unix.NonWindowsGetDomainName error: " + lastError); } - else + + int index = fullyQualifiedName.IndexOf('.'); + if (index >= 0) { - throw new PlatformNotSupportedException(); + return fullyQualifiedName.Substring(index + 1); } + + return ""; } internal static string NonWindowsGetUserName() { - if (!IsWindows) - { - return LinuxPlatform.UserName; - } - else - { - throw new PlatformNotSupportedException(); - } + return Unix.UserName; } // Hostname in this context seems to be the FQDN internal static string NonWindowsGetHostName() { - if (!IsWindows) + string hostName = Unix.NativeMethods.GetFullyQualifiedName(); + if (string.IsNullOrEmpty(hostName)) { - string hostName = LinuxPlatform.Native.GetFullyQualifiedName(); - if (string.IsNullOrEmpty(hostName)) - { - int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.NonWindowsHostName error: " + lastError); - } - return hostName; + int lastError = Marshal.GetLastWin32Error(); + throw new InvalidOperationException("Unix.NonWindowsHostName error: " + lastError); + } + return hostName; + } - } - else - { - throw new PlatformNotSupportedException(); - } + internal static bool NonWindowsIsFile(string path) + { + return Unix.NativeMethods.IsFile(path); + } + + internal static bool NonWindowsIsDirectory(string path) + { + return Unix.NativeMethods.IsDirectory(path); } internal static bool NonWindowsIsExecutable(string path) { - if (!IsWindows) - { - return LinuxPlatform.IsExecutable(path); - } - - throw new PlatformNotSupportedException(); + return Unix.NativeMethods.IsExecutable(path); } internal static uint NonWindowsGetThreadId() @@ -512,15 +353,6 @@ namespace System.Management.Automation return 0; } - /// - /// This exception is meant to be thrown if a code path is not supported due - /// to platform restrictions - /// - internal class PlatformNotSupportedException : System.Exception - { - public PlatformNotSupportedException() : base() {} - } - /// /// This models the native call CommandLineToArgvW in managed code. /// @@ -581,7 +413,7 @@ namespace System.Management.Automation } } - internal static class LinuxPlatform + internal static class Unix { private static string _userName; public static string UserName @@ -590,11 +422,11 @@ namespace System.Management.Automation { if (string.IsNullOrEmpty(_userName)) { - _userName = Native.GetUserName(); + _userName = NativeMethods.GetUserName(); if (string.IsNullOrEmpty(_userName)) { int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.UserName error: " + lastError); + throw new InvalidOperationException("Unix.UserName error: " + lastError); } } return _userName; @@ -620,40 +452,40 @@ namespace System.Management.Automation } } -#if CORECLR + #if CORECLR public static string GetFolderPath(SpecialFolder folder) { string s = null; switch (folder) { - case SpecialFolder.ProgramFiles: - s = "/bin"; - break; - case SpecialFolder.ProgramFilesX86: - s = "/usr/bin"; - break; - case SpecialFolder.System: - s = "/sbin"; - break; - case SpecialFolder.SystemX86: - s = "/sbin"; - break; - case SpecialFolder.Personal: - s = System.Environment.GetEnvironmentVariable("HOME"); - break; - case SpecialFolder.LocalApplicationData: - s = System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".config"); - if (!System.IO.Directory.Exists(s)) - { - System.IO.Directory.CreateDirectory(s); - } - break; - default: - throw new NotSupportedException(); + case SpecialFolder.ProgramFiles: + s = "/bin"; + break; + case SpecialFolder.ProgramFilesX86: + s = "/usr/bin"; + break; + case SpecialFolder.System: + s = "/sbin"; + break; + case SpecialFolder.SystemX86: + s = "/sbin"; + break; + case SpecialFolder.Personal: + s = System.Environment.GetEnvironmentVariable("HOME"); + break; + case SpecialFolder.LocalApplicationData: + s = System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("HOME"), ".config"); + if (!System.IO.Directory.Exists(s)) + { + System.IO.Directory.CreateDirectory(s); + } + break; + default: + throw new NotSupportedException(); } return s; } -#endif + #endif public static bool IsHardLink(ref IntPtr handle) { @@ -671,7 +503,7 @@ namespace System.Management.Automation int count; string filePath = fs.FullName; - int ret = Native.GetLinkCount(filePath, out count); + int ret = NativeMethods.GetLinkCount(filePath, out count); if (ret == 1) { return count > 1; @@ -679,84 +511,27 @@ namespace System.Management.Automation else { int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.IsHardLink error: " + lastError); + throw new InvalidOperationException("Unix.IsHardLink error: " + lastError); } } - public static bool IsSymLink(FileSystemInfo fs) - { - if (!fs.Exists) - { - return false; - } - - string filePath = fs.FullName; - int ret = Native.IsSymLink(filePath); - switch(ret) - { - case 1: - return true; - case 0: - return false; - default: - int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.IsSymLink error: " + lastError); - } - } - - public static bool IsExecutable(string filePath) - { - if (!File.Exists(filePath)) - { - return false; - } - - int ret = Native.IsExecutable(filePath); - switch(ret) - { - case 1: - return true; - case 0: - return false; - default: - int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.IsExecutable error: " + lastError); - } - } - public static void SetDate(SetDateInfoInternal info) { - int ret = Native.SetDate(info); + int ret = NativeMethods.SetDate(info); if (ret == -1) { int lastError = Marshal.GetLastWin32Error(); - throw new InvalidOperationException("LinuxPlatform.NonWindowsSetDate error: " + lastError); + throw new InvalidOperationException("Unix.NonWindowsSetDate error: " + lastError); } } - public static bool CreateSymbolicLink(string path, string strTargetPath) - { - int ret = Native.CreateSymLink(path, strTargetPath); - return ret == 1 ? true : false; - } - public static bool CreateHardLink(string path, string strTargetPath) { - int ret = Native.CreateHardLink(path, strTargetPath); + int ret = NativeMethods.CreateHardLink(path, strTargetPath); return ret == 1 ? true : false; } - public static string FollowSymLink(string path) - { - return Native.FollowSymLink(path); - } - - public static string GetUserFromPid(int pid) - { - return Native.GetUserFromPid(pid); - } - [StructLayout(LayoutKind.Sequential)] internal class SetDateInfoInternal { @@ -788,12 +563,14 @@ namespace System.Management.Automation } } - internal static class Native + internal static class NativeMethods { const string psLib = "libpsl-native"; // Ansi is a misnomer, it is hardcoded to UTF-8 on Linux and OS X + // C bools are 1 byte and so must be marshaled as I1 + [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] [return: MarshalAs(UnmanagedType.LPStr)] internal static extern string GetUserName(); @@ -802,10 +579,12 @@ namespace System.Management.Automation internal static extern int GetLinkCount([MarshalAs(UnmanagedType.LPStr)]string filePath, out int linkCount); [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int IsSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath); + [return: MarshalAs(UnmanagedType.I1)] + internal static extern bool IsSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath); [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int IsExecutable([MarshalAs(UnmanagedType.LPStr)]string filePath); + [return: MarshalAs(UnmanagedType.I1)] + internal static extern bool IsExecutable([MarshalAs(UnmanagedType.LPStr)]string filePath); [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] [return: MarshalAs(UnmanagedType.LPStr)] @@ -815,7 +594,8 @@ namespace System.Management.Automation internal static extern int SetDate(SetDateInfoInternal info); [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int CreateSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath, + [return: MarshalAs(UnmanagedType.I1)] + internal static extern bool CreateSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath, [MarshalAs(UnmanagedType.LPStr)]string target); [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] @@ -829,6 +609,15 @@ namespace System.Management.Automation [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] [return: MarshalAs(UnmanagedType.LPStr)] internal static extern string GetUserFromPid(int pid); + + [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I1)] + internal static extern bool IsFile([MarshalAs(UnmanagedType.LPStr)]string filePath); + + [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I1)] + internal static extern bool IsDirectory([MarshalAs(UnmanagedType.LPStr)]string filePath); + } } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 8c3eb882bf..d3fba6c141 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -4464,12 +4464,9 @@ namespace System.Management.Automation internal static List GetFileShares(string machine, bool ignoreHidden) { - // Platform notes: don't throw an exception since this is being used in auto-completion - if (!Platform.HasFileShares()) - { - return new List(); - } - +#if LINUX + return new List(); +#else IntPtr shBuf; uint numEntries; uint totalEntries; @@ -4495,6 +4492,7 @@ namespace System.Management.Automation } } return shares; +#endif } private static bool CheckFileExtension(string path, HashSet extension) diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index 2d0e2e917e..23c95b4324 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -1903,13 +1903,9 @@ namespace System.Management.Automation /// internal static string GetShellPathFromRegistry(string shellID) { - if (!Platform.HasRegistrySupport()) - { - return null; - } - string result = null; +#if !LINUX try { RegistryKey shellKey = Registry.LocalMachine.OpenSubKey(Utils.GetRegistryConfigurationPath(shellID)); @@ -1935,6 +1931,7 @@ namespace System.Management.Automation catch (ArgumentException) { } +#endif return result; } diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index d500e7e857..92aaee14eb 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -103,13 +103,9 @@ namespace System.Management.Automation // Gets the current WSMan stack version from the registry. private static Version GetWSManStackVersion() { - if (!Platform.HasRegistrySupport()) - { - return new Version(1, 0); - } - Version version = null; +#if !LINUX try { using (RegistryKey wsManStackVersionKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\WSMAN")) @@ -133,6 +129,7 @@ namespace System.Management.Automation catch (FormatException) { } catch (OverflowException) { } catch (InvalidCastException) { } +#endif return version ?? System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STACK_VERSION; } diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index d7e0f8e384..6de20eee21 100644 --- a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs @@ -1469,11 +1469,12 @@ namespace System.Management.Automation bool driveIsValid = true; // If the drive is auto-mounted, ensure that it still exists, or remove the drive. - // note: not all platforms support drive auto-mounting - if (Platform.HasDriveAutoMounting() && (drive.IsAutoMounted || IsAStaleVhdMountedDrive(drive))) +#if !LINUX + if (drive.IsAutoMounted || IsAStaleVhdMountedDrive(drive)) { driveIsValid = ValidateOrRemoveAutoMountedDrive(drive, lookupScope); } +#endif if(drive.Name.Length == 1) { if(!(driveNames.Contains(drive.Name))) diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8dd8f96bea..d5b29ad010 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -62,15 +62,6 @@ namespace System.Management.Automation return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8)); } - /// - /// IsWinPEHost indicates if the machine on which PowerShell is hosted is WinPE or not. - /// This is a helper variable used to kep track if the IsWinPE() helper method has - /// already checked for the WinPE specific registry key or not. - /// If the WinPE specific registry key has not yet been checked even - /// once then this variable will point to null. - /// - internal static bool? isWinPEHost = null; - /// /// The existence of the following registry confirms that the host machine is a WinPE /// HKLM\System\CurrentControlSet\Control\MiniNT @@ -368,58 +359,31 @@ namespace System.Management.Automation /// internal static bool IsWinPEHost() { - if (Platform.HasRegistrySupport()) - { - return WinIsWinPEHost(); - } - else - { - return false; - } - } - - internal static bool WinIsWinPEHost() - { +#if !LINUX RegistryKey winPEKey = null; - if (!isWinPEHost.HasValue) + try { - try - { - // The existence of the following registry confirms that the host machine is a WinPE - // HKLM\System\CurrentControlSet\Control\MiniNT - winPEKey = Registry.LocalMachine.OpenSubKey(WinPEIdentificationRegKey); + // The existence of the following registry confirms that the host machine is a WinPE + // HKLM\System\CurrentControlSet\Control\MiniNT + winPEKey = Registry.LocalMachine.OpenSubKey(WinPEIdentificationRegKey); - if (null != winPEKey) - { - isWinPEHost = true; - } - else - { - isWinPEHost = false; - } - } - catch (ArgumentException) { } - catch (SecurityException) { } - catch (ObjectDisposedException) { } - finally + return winPEKey != null; + } + catch (ArgumentException) { } + catch (SecurityException) { } + catch (ObjectDisposedException) { } + finally + { + if (winPEKey != null) { - if (winPEKey != null) - { - winPEKey.Dispose(); - } + winPEKey.Dispose(); } } +#endif + return false; + } - if (isWinPEHost.HasValue) - { - return (bool)isWinPEHost; - } - else - { - return false; - } - } #region Versioning related methods @@ -652,13 +616,9 @@ namespace System.Management.Automation internal static Dictionary GetGroupPolicySetting(string groupPolicyBase, string settingName, RegistryKey[] preferenceOrder) { - if (!Platform.HasGroupPolicySupport()) - { - // Porting note: No group policy support means we have an empty set of - // policies. - return _emptyDictionary; - } - +#if LINUX + return _emptyDictionary; +#else lock (cachedGroupPolicySettings) { // Return cached information, if we have it @@ -733,6 +693,7 @@ namespace System.Management.Automation return settings; } +#endif } static ConcurrentDictionary> cachedGroupPolicySettings = new ConcurrentDictionary>(); @@ -984,38 +945,9 @@ namespace System.Management.Automation return NativeItemExists(path, out unusedIsDirectory, out unusedException); } + // This is done through P/Invoke since File.Exists and Directory.Exists pay 13% performance degradation + // through the CAS checks, and are terribly slow for network paths. internal static bool NativeItemExists(string path, out bool isDirectory, out Exception exception) - { - if (Platform.UseDotNetToQueryFileAttributes()) - { - // TODO:PSL replace by System.IO.File.GetAttributes - exception = null; - if (NativeDirectoryExists(path)) - { - isDirectory = true; - return true; - } - else if (NativeFileExists(path)) - { - isDirectory = false; - return true; - } - else - { - isDirectory = false; - return false; - } - } - else - { - // This is done through P/Invoke since File.Exists and Directory.Exists - // pay 13% performance degradation through the CAS checks, and are - // terribly slow for network paths. - return WinNativeItemExists(path,out isDirectory,out exception); - } - } - - internal static bool WinNativeItemExists(string path, out bool isDirectory, out Exception exception) { exception = null; @@ -1024,6 +956,10 @@ namespace System.Management.Automation isDirectory = false; return false; } +#if LINUX + isDirectory = Platform.NonWindowsIsDirectory(path); + return Platform.NonWindowsIsFile(path); +#else if (IsReservedDeviceName(path)) { @@ -1057,23 +993,12 @@ namespace System.Management.Automation ((int)NativeMethods.FileAttributes.Directory); return true; +#endif } + // This is done through P/Invoke since we pay 13% performance degradation + // through the CAS checks required by File.Exists and Directory.Exists internal static bool NativeFileExists(string path) - { - if (Platform.UseDotNetToQueryFileAttributes()) - { - return System.IO.File.Exists(path); - } - else - { - // This is done through P/Invoke since we pay 13% performance degradation - // through the CAS checks required by File.Exists and Directory.Exists - return WinNativeFileExists(path); - } - } - - internal static bool WinNativeFileExists(string path) { bool isDirectory; Exception ioException; @@ -1090,18 +1015,6 @@ namespace System.Management.Automation // This is done through P/Invoke since we pay 13% performance degradation // through the CAS checks required by File.Exists and Directory.Exists internal static bool NativeDirectoryExists(string path) - { - if (Platform.UseDotNetToQueryFileAttributes()) - { - return System.IO.Directory.Exists(path); - } - else - { - return WinNativeDirectoryExists(path); - } - } - - internal static bool WinNativeDirectoryExists(string path) { bool isDirectory; Exception ioException; @@ -1116,31 +1029,6 @@ namespace System.Management.Automation } internal static void NativeEnumerateDirectory(string directory, out List directories, out List files) - { - if (Platform.UseDotNetToQueryFileAttributes()) - { - IEnumerable fileEnum = System.IO.Directory.EnumerateFiles(directory); - IEnumerable dirEnum = System.IO.Directory.EnumerateDirectories(directory); - - files = new List(); - directories = new List(); - - foreach (string entry in fileEnum) - { - files.Add(directory + "/" + entry); - } - foreach (string entry in dirEnum) - { - directories.Add(directory + "/" + entry); - } - } - else - { - WinNativeEnumerateDirectory(directory, out directories, out files); - } - } - - internal static void WinNativeEnumerateDirectory(string directory, out List directories, out List files) { IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); NativeMethods.WIN32_FIND_DATA findData; @@ -1205,12 +1093,12 @@ namespace System.Management.Automation internal static bool PathIsUnc(string path) { - if (!Platform.HasUNCSupport()) - { - return false; - } +#if LINUX + return false; +#else Uri uri; return !string.IsNullOrEmpty(path) && Uri.TryCreate(path, UriKind.Absolute, out uri) && uri.IsUnc; +#endif } internal class NativeMethods diff --git a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs index 2f0fa35d53..c3e9cc0066 100644 --- a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs +++ b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs @@ -96,12 +96,10 @@ namespace Microsoft.PowerShell.Commands /// internal static void CheckRemotingCmdletPrerequisites() { - //If the operating system (ie Linux) does not support the registry, simply return to avoid calls to the Windows registry. - if (!Platform.HasRegistrySupport()) - { - return; - } - +#if LINUX + // TODO: check that PSRP requirements are installed + return; +#else bool notSupported = true; String WSManKeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\WSMAN\\"; @@ -155,6 +153,7 @@ namespace Microsoft.PowerShell.Commands throw new InvalidOperationException( "Windows PowerShell remoting features are not enabled or not supported on this machine.\nThis may be because you do not have the correct version of WS-Management installed or this version of Windows does not support remoting currently.\n For more information, type 'get-help about_remote_requirements'."); } +#endif } /// diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 50592a4e4d..8d598b13bc 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -677,14 +677,11 @@ namespace System.Management.Automation.Language private static TypeInfo GetComObjectType() { - if (Platform.HasCom()) - { - return typeof(object).GetTypeInfo().Assembly.GetType("System.__ComObject").GetTypeInfo(); - } - else - { - return null; - } +#if LINUX + return null; +#else + return typeof(object).GetTypeInfo().Assembly.GetType("System.__ComObject").GetTypeInfo(); +#endif } internal static bool IsComObject(object obj) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 39c60e005b..225caeffc7 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -496,7 +496,7 @@ namespace Microsoft.PowerShell.Commands } else { - throw new Platform.PlatformNotSupportedException(); + throw new PlatformNotSupportedException(); } } @@ -607,15 +607,11 @@ namespace Microsoft.PowerShell.Commands /// protected override PSDriveInfo RemoveDrive(PSDriveInfo drive) { - // if removing the drive is not supported, just return the drive - if (Platform.SupportsRemoveDrive()) - { - return WinRemoveDrive(drive); - } - else - { - return drive; - } +#if LINUX + return drive; +#else + return WinRemoveDrive(drive); +#endif } private PSDriveInfo WinRemoveDrive(PSDriveInfo drive) @@ -685,15 +681,11 @@ namespace Microsoft.PowerShell.Commands /// internal static string GetUNCForNetworkDrive(string driveName) { - // Porting note: on systems that do not support UNC paths, the UNC equivalent is the path itself - if (Platform.HasUNCSupport()) - { - return WinGetUNCForNetworkDrive(driveName); - } - else - { - return driveName; - } +#if LINUX + return driveName; +#else + return WinGetUNCForNetworkDrive(driveName); +#endif } private static string WinGetUNCForNetworkDrive(string driveName) @@ -747,14 +739,11 @@ namespace Microsoft.PowerShell.Commands /// internal static string GetSubstitutedPathForNetworkDosDevice(string driveName) { - if (Platform.HasNetworkDriveSupport()) - { - return WinGetSubstitutedPathForNetworkDosDevice(driveName); - } - else - { - throw new Platform.PlatformNotSupportedException(); - } +#if LINUX + throw new PlatformNotSupportedException(); +#else + return WinGetSubstitutedPathForNetworkDosDevice(driveName); +#endif } private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName) @@ -926,15 +915,15 @@ namespace Microsoft.PowerShell.Commands if (newDrive.DriveType == DriveType.Network) { // Platform notes: This is important because certain mount - // points on non-Windows are enumerated as drives by .net, but + // points on non-Windows are enumerated as drives by .NET, but // the platform itself then has no real network drive support // as required by this context. Solution: check for network // drive support before using it. - if (!Platform.HasNetworkDriveSupport()) - { - continue; - } +#if LINUX + continue; +#else displayRoot = GetRootPathForNetworkDriveOrDosDevice(newDrive); +#endif } if (newDrive.DriveType == DriveType.Fixed) @@ -947,12 +936,14 @@ namespace Microsoft.PowerShell.Commands root = newDrive.RootDirectory.FullName; } +#if LINUX // Porting notes: On platforms with single root filesystems, only // add the filesystem with the root "/" to the initial drive list, // otherwise path handling will not work correctly because there // is no : available to separate the filesystems from each other - if (Platform.HasSingleRootFilesystem() && root != StringLiterals.DefaultPathSeparatorString) + if (root != StringLiterals.DefaultPathSeparatorString) continue; +#endif // Porting notes: On non-windows platforms .net can report two // drives with the same root, make sure to only add one of those @@ -2229,7 +2220,7 @@ namespace Microsoft.PowerShell.Commands } else { - success = Platform.NonWindowsCreateSymbolicLink(path,strTargetPath,isDirectory); + success = Platform.NonWindowsCreateSymbolicLink(path,strTargetPath); } } else if(itemType == ItemType.HardLink) @@ -6971,48 +6962,47 @@ namespace Microsoft.PowerShell.Commands internal static int SafeGetFileAttributes(string path) { - if (Platform.UseDotNetToQueryFileAttributes()) - { - System.IO.FileAttributes attr = System.IO.File.GetAttributes(path); +#if LINUX + System.IO.FileAttributes attr = System.IO.File.GetAttributes(path); - int result = 0; - if ((attr & FileAttributes.Archive) == FileAttributes.Archive) - result |= 0x20; - if ((attr & FileAttributes.Compressed) == FileAttributes.Compressed) - result |= 0x800; - if ((attr & FileAttributes.Device) == FileAttributes.Device) - result |= 0x40; - if ((attr & FileAttributes.Directory) == FileAttributes.Directory) - result |= 0x10; - if ((attr & FileAttributes.Encrypted) == FileAttributes.Encrypted) - result |= 0x4000; - if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden) - result |= 0x2; - if ((attr & FileAttributes.IntegrityStream) == FileAttributes.IntegrityStream) - result |= 0x8000; - if ((attr & FileAttributes.Normal) == FileAttributes.Normal) - result |= 0x80; - if ((attr & FileAttributes.NoScrubData) == FileAttributes.NoScrubData) - result |= 0x20000; - if ((attr & FileAttributes.NotContentIndexed) == FileAttributes.NotContentIndexed) - result |= 0x2000; - if ((attr & FileAttributes.Offline) == FileAttributes.Offline) - result |= 0x1000; - if ((attr & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - result |= 0x1; - if ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) - result |= 0x400; - if ((attr & FileAttributes.SparseFile) == FileAttributes.SparseFile) - result |= 0x200; - if ((attr & FileAttributes.System) == FileAttributes.System) - result |= 0x4; - if ((attr & FileAttributes.Temporary) == FileAttributes.Temporary) - result |= 0x100; + int result = 0; + if ((attr & FileAttributes.Archive) == FileAttributes.Archive) + result |= 0x20; + if ((attr & FileAttributes.Compressed) == FileAttributes.Compressed) + result |= 0x800; + if ((attr & FileAttributes.Device) == FileAttributes.Device) + result |= 0x40; + if ((attr & FileAttributes.Directory) == FileAttributes.Directory) + result |= 0x10; + if ((attr & FileAttributes.Encrypted) == FileAttributes.Encrypted) + result |= 0x4000; + if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden) + result |= 0x2; + if ((attr & FileAttributes.IntegrityStream) == FileAttributes.IntegrityStream) + result |= 0x8000; + if ((attr & FileAttributes.Normal) == FileAttributes.Normal) + result |= 0x80; + if ((attr & FileAttributes.NoScrubData) == FileAttributes.NoScrubData) + result |= 0x20000; + if ((attr & FileAttributes.NotContentIndexed) == FileAttributes.NotContentIndexed) + result |= 0x2000; + if ((attr & FileAttributes.Offline) == FileAttributes.Offline) + result |= 0x1000; + if ((attr & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + result |= 0x1; + if ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) + result |= 0x400; + if ((attr & FileAttributes.SparseFile) == FileAttributes.SparseFile) + result |= 0x200; + if ((attr & FileAttributes.System) == FileAttributes.System) + result |= 0x4; + if ((attr & FileAttributes.Temporary) == FileAttributes.Temporary) + result |= 0x100; - return result; - } - else - return WinSafeGetFileAttributes(path); + return result; +#else + return WinSafeGetFileAttributes(path); +#endif } internal static int WinSafeGetFileAttributes(string path) @@ -7074,11 +7064,11 @@ namespace Microsoft.PowerShell.Commands /// internal static bool PathIsNetworkPath(string path) { - // no other logic possible for now on linux - if (Platform.HasNetworkDriveSupport()) - return WinPathIsNetworkPath(path); - else - return false; +#if LINUX + return false; +#else + return WinPathIsNetworkPath(path); +#endif } internal static bool WinPathIsNetworkPath(string path) @@ -8202,7 +8192,7 @@ namespace Microsoft.PowerShell.Commands { if (!Platform.IsWindows) { - throw new Platform.PlatformNotSupportedException(); + throw new PlatformNotSupportedException(); } using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericRead)) @@ -8595,14 +8585,11 @@ namespace Microsoft.PowerShell.Commands private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode) { - if (Platform.SupportsReparsePoints()) - { - return WinOpenReparsePoint(reparsePoint,accessMode); - } - else - { - throw new Platform.PlatformNotSupportedException(); - } +#if LINUX + throw new PlatformNotSupportedException(); +#else + return WinOpenReparsePoint(reparsePoint,accessMode); +#endif } private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode) diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index 313e9e0e6c..c150dd7179 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -1589,9 +1589,12 @@ namespace System.Management.Automation /// internal static bool IsSingleFileSystemAbsolutePath(string path) { - return Platform.HasSingleRootFilesystem() && - (path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) || - path.StartsWith(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal)); +#if LINUX + return path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) + || path.StartsWith(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal); +#else + return false; +#endif } // IsSingleFileSystemAbsolutePath /// diff --git a/src/System.Management.Automation/namespaces/RegistryProvider.cs b/src/System.Management.Automation/namespaces/RegistryProvider.cs index 1951f59eea..9a168ff7f8 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -136,11 +136,7 @@ namespace Microsoft.PowerShell.Commands { Collection drives = new Collection(); - if (!Platform.HasRegistrySupport()) - { - return drives; - } - +#if !LINUX drives.Add( new PSDriveInfo( "HKLM", @@ -156,6 +152,7 @@ namespace Microsoft.PowerShell.Commands "HKEY_CURRENT_USER", RegistryProviderStrings.HKCUDriveDescription, null)); +#endif return drives; } // InitializeDefaultDrives diff --git a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs b/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs index 1e817966b9..80120a7025 100644 --- a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs +++ b/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs @@ -58,13 +58,13 @@ namespace Microsoft.PowerShell.Commands.Internal override protected bool ReleaseHandle() { - if (!Platform.HasRegistrySupport()) - { - return true; - } +#if LINUX + return true; +#else // Returns a Win32 error code, 0 for success int r = RegCloseKey(handle); return r == 0; +#endif } } } diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 05ae9f77dc..8d3fc91f2d 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -138,11 +138,9 @@ namespace System.Management.Automation internal static void SetExecutionPolicy(ExecutionPolicyScope scope, ExecutionPolicy policy, string shellId) { - if (!Platform.HasExecutionPolicy()) - { - throw new PlatformNotSupportedException(); - } - +#if LINUX + throw new PlatformNotSupportedException(); +#else string executionPolicy = "Restricted"; string preferenceKey = Utils.GetRegistryConfigurationPath(shellId); const string PolicyKeyValueName = "ExecutionPolicy"; @@ -227,6 +225,7 @@ namespace System.Management.Automation break; } } +#endif } // Clean up the parents of a registry key as long as they @@ -283,11 +282,9 @@ namespace System.Management.Automation internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPolicyScope scope) { - if (!Platform.HasExecutionPolicy()) - { - throw new PlatformNotSupportedException(); - } - +#if LINUX + return ExecutionPolicy.Unrestricted; +#else switch (scope) { case ExecutionPolicyScope.Process: @@ -368,6 +365,7 @@ namespace System.Management.Automation } return ExecutionPolicy.Restricted; +#endif } internal static ExecutionPolicy ParseExecutionPolicy(string policy) @@ -1633,14 +1631,11 @@ namespace System.Management.Automation /// AMSI_RESULT_DETECTED if malware was detected in the sample. internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string sourceMetadata) { - if (Platform.HasAmsi()) - { - return WinScanContent(content,sourceMetadata); - } - else - { - return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; - } +#if LINUX + return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; +#else + return WinScanContent(content,sourceMetadata); +#endif } internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata) @@ -1730,14 +1725,9 @@ namespace System.Management.Automation internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) { - if (Platform.HasAmsi()) - { - VerifyAmsiUninitializeCalled(); - } - else - { - throw new PlatformNotSupportedException(); - } +#if !LINUX + VerifyAmsiUninitializeCalled(); +#endif } [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] @@ -1753,16 +1743,9 @@ namespace System.Management.Automation /// internal static void CloseSession() { - if (Platform.HasAmsi()) - { - WinCloseSession(); - } - else - { - // Porting note: cannot throw here because this is called whether or not - // AMSI was initialized in the first place - return; - } +#if !LINUX + WinCloseSession(); +#endif } internal static void WinCloseSession() @@ -1789,16 +1772,9 @@ namespace System.Management.Automation /// internal static void Uninitialize() { - if (Platform.HasAmsi()) - { - WinUninitialize(); - } - else - { - // Porting note: cannot throw here because this is called whether or not - // AMSI was initialized in the first place - return; - } +#if !LINUX + WinUninitialize(); +#endif } internal static void WinUninitialize() diff --git a/src/System.Management.Automation/utils/ExtensionMethods.cs b/src/System.Management.Automation/utils/ExtensionMethods.cs index 0e58cc55f4..1c1c76f199 100644 --- a/src/System.Management.Automation/utils/ExtensionMethods.cs +++ b/src/System.Management.Automation/utils/ExtensionMethods.cs @@ -130,12 +130,9 @@ namespace System.Management.Automation [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsComObject(this Type type) { - if (!Platform.HasCom()) - { - return false; - } - -#if CORECLR // Type.IsComObject(Type) is not in CoreCLR +#if LINUX + return false; +#elif CORECLR // Type.IsComObject(Type) is not in CoreCLR return ComObjectType.IsAssignableFrom(type); #else return type.IsCOMObject; diff --git a/src/libpsl-native/src/CMakeLists.txt b/src/libpsl-native/src/CMakeLists.txt index 8b3d0f19a9..5d4c432b40 100644 --- a/src/libpsl-native/src/CMakeLists.txt +++ b/src/libpsl-native/src/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(psl-native SHARED getstat.cpp + getlstat.cpp getpwuid.cpp getuserfrompid.cpp getfileowner.cpp @@ -8,6 +9,8 @@ add_library(psl-native SHARED getcomputername.cpp getlinkcount.cpp getfullyqualifiedname.cpp + isfile.cpp + isdirectory.cpp issymlink.cpp isexecutable.cpp setdate.cpp diff --git a/src/libpsl-native/src/createsymlink.cpp b/src/libpsl-native/src/createsymlink.cpp index 4ea8d6fd9d..b9ce2b18d1 100644 --- a/src/libpsl-native/src/createsymlink.cpp +++ b/src/libpsl-native/src/createsymlink.cpp @@ -39,26 +39,25 @@ //! - ERROR_INVALID_FUNCTION: incorrect function //! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters //! -//! @retval 1 if creation is successful -//! @retval 0 if createion failed +//! @retval boolean successful //! -int32_t CreateSymLink(const char *link, const char *target) +bool CreateSymLink(const char *link, const char *target) { - errno = 0; + errno = 0; // Check parameters if (!link || !target) { errno = ERROR_INVALID_PARAMETER; - return 0; + return false; } - int returnCode = symlink(target, link); + int ret = symlink(target, link); - if (returnCode == 0) + if (ret == 0) { - return 1; + return true; } switch(errno) @@ -102,5 +101,6 @@ int32_t CreateSymLink(const char *link, const char *target) default: errno = ERROR_INVALID_FUNCTION; } - return 0; + + return false; } diff --git a/src/libpsl-native/src/createsymlink.h b/src/libpsl-native/src/createsymlink.h index e47bce3fa1..45da928902 100644 --- a/src/libpsl-native/src/createsymlink.h +++ b/src/libpsl-native/src/createsymlink.h @@ -1,9 +1,10 @@ #pragma once #include "pal.h" +#include PAL_BEGIN_EXTERNC -int32_t CreateSymLink(const char *link, const char *target); +bool CreateSymLink(const char *link, const char *target); PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getlstat.cpp b/src/libpsl-native/src/getlstat.cpp new file mode 100644 index 0000000000..d2fcb69697 --- /dev/null +++ b/src/libpsl-native/src/getlstat.cpp @@ -0,0 +1,95 @@ +//! @file getlstat.cpp +//! @author Andrew Schwartzmeyer +//! @brief returns the lstat of a file + +#include +#include +#include +#include +#include +#include +#include "getlstat.h" + +//! @brief GetLStat returns the lstat of a file. This simply delegates to the +//! lstat() system call and maps errno to the expected values for GetLastError. +//! +//! GetLstat +//! +//! @param[in] path +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[in] lstat +//! @parblock +//! A pointer to the buffer in which to place the lstat information +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: too many symbolic links +//! - ERROR_GEN_FAILURE: I/O error occurred +//! - ERROR_INVALID_NAME: file provided is not a symbolic link +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long +//! - ERROR_OUTOFMEMORY insufficient kernel memory +//! +//! @retval 0 if successful +//! @retval -1 if failed +//! + +int32_t GetLStat(const char* path, struct stat* buf) +{ + errno = 0; + + if (!path) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + int32_t ret = lstat(path, buf); + + if (ret != 0) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EBADF: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_NO_SUCH_USER; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case EOVERFLOW: + errno = ERROR_BUFFER_OVERFLOW; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + } + + return ret; +} diff --git a/src/libpsl-native/src/getlstat.h b/src/libpsl-native/src/getlstat.h new file mode 100644 index 0000000000..ba746dd35a --- /dev/null +++ b/src/libpsl-native/src/getlstat.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t GetLStat(const char* path, struct stat* buf); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getstat.cpp b/src/libpsl-native/src/getstat.cpp index 3e89d1a6ed..8fd6662219 100644 --- a/src/libpsl-native/src/getstat.cpp +++ b/src/libpsl-native/src/getstat.cpp @@ -89,8 +89,7 @@ int32_t GetStat(const char* path, struct stat* buf) default: errno = ERROR_INVALID_FUNCTION; } - return -1; } - return 0; + return ret; } diff --git a/src/libpsl-native/src/isdirectory.cpp b/src/libpsl-native/src/isdirectory.cpp new file mode 100644 index 0000000000..f8b6bfe742 --- /dev/null +++ b/src/libpsl-native/src/isdirectory.cpp @@ -0,0 +1,50 @@ +//! @file isdirectory.cpp +//! @author Andrew Schwartzmeyer +//! @brief returns if the path is a directory + +#include +#include +#include +#include +#include +#include +#include "getstat.h" +#include "getpwuid.h" +#include "getfileowner.h" +#include "isdirectory.h" + +//! @brief returns if the path is a directory; uses stat and so follows symlinks +//! +//! IsDirectory +//! +//! @param[in] path +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes this error via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! +//! @retval true if directory, false otherwise +//! +bool IsDirectory(const char* path) +{ + errno = 0; + + if (!path) + { + errno = ERROR_INVALID_PARAMETER; + return false; + } + + struct stat buf; + int32_t ret = GetStat(path, &buf); + if (ret != 0) + { + return false; + } + + return S_ISDIR(buf.st_mode); +} diff --git a/src/libpsl-native/src/isdirectory.h b/src/libpsl-native/src/isdirectory.h new file mode 100644 index 0000000000..746d1c5793 --- /dev/null +++ b/src/libpsl-native/src/isdirectory.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" +#include + +PAL_BEGIN_EXTERNC + +bool IsDirectory(const char* path); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/isexecutable.cpp b/src/libpsl-native/src/isexecutable.cpp index 38f6ad1844..a06fab57ab 100644 --- a/src/libpsl-native/src/isexecutable.cpp +++ b/src/libpsl-native/src/isexecutable.cpp @@ -1,5 +1,5 @@ -//! @file isExecutable.cpp -//! @author George FLeming +//! @file isexecutable.cpp +//! @author George Fleming //! @brief returns whether a file is executable #include @@ -11,7 +11,7 @@ //! //! IsExecutable //! -//! @param[in] fileName +//! @param[in] path //! @parblock //! A pointer to the buffer that contains the file name //! @@ -19,63 +19,21 @@ //! @endparblock //! //! @exception errno Passes these errors via errno to GetLastError: -//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified //! - ERROR_INVALID_ADDRESS: attempt to access invalid address -//! - ERROR_GEN_FAILURE: device attached to the system is not functioning -//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect -//! - ERROR_INVALID_FUNCTION: incorrect function -//! - ERROR_INVALID_PARAMETER: parameter to access(2) call is incorrect //! -//! @retval 1 if path is an executable -//! @retval 0 if path is not a executable -//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! @retval true if path is an executable, false otherwise //! -int32_t IsExecutable(const char* fileName) +bool IsExecutable(const char* path) { errno = 0; // Check parameters - if (!fileName) + if (!path) { errno = ERROR_INVALID_PARAMETER; - return -1; + return false; } - int returnCode = access(fileName, X_OK); - - if (returnCode == 0) - { - return 1; - } - - switch(errno) - { - case EACCES: - return 0; - case EBADF: - case ENOENT: - errno = ERROR_FILE_NOT_FOUND; - break; - case EFAULT: - errno = ERROR_INVALID_ADDRESS; - break; - case ELOOP: - errno = ERROR_STOPPED_ON_SYMLINK; - break; - case EIO: - case ENOMEM: - errno = ERROR_GEN_FAILURE; - break; - case ENOTDIR: - case ENAMETOOLONG: - errno = ERROR_INVALID_NAME; - break; - case EINVAL: - errno = ERROR_INVALID_PARAMETER; - break; - default: - errno = ERROR_INVALID_FUNCTION; - } - return -1; + return access(path, X_OK) != -1; } diff --git a/src/libpsl-native/src/isexecutable.h b/src/libpsl-native/src/isexecutable.h index da4e9f8f36..f4278b954e 100644 --- a/src/libpsl-native/src/isexecutable.h +++ b/src/libpsl-native/src/isexecutable.h @@ -1,9 +1,10 @@ #pragma once #include "pal.h" +#include PAL_BEGIN_EXTERNC -int32_t IsExecutable(const char* fileName); +bool IsExecutable(const char* path); PAL_END_EXTERNC diff --git a/src/libpsl-native/src/isfile.cpp b/src/libpsl-native/src/isfile.cpp new file mode 100644 index 0000000000..1f6d0c59e5 --- /dev/null +++ b/src/libpsl-native/src/isfile.cpp @@ -0,0 +1,43 @@ +//! @file isfile.cpp +//! @author Andrew Schwartzmeyer +//! @brief returns if the path exists + +#include +#include +#include +#include +#include +#include +#include +#include "getlstat.h" +#include "isfile.h" + +//! @brief returns if the path is a file or directory +//! +//! IsFile +//! +//! @param[in] path +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes this error via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! +//! @retval true if path exists, false otherwise +//! +bool IsFile(const char* path) +{ + errno = 0; + + if (!path) + { + errno = ERROR_INVALID_PARAMETER; + return false; + } + + struct stat buf; + return GetLStat(path, &buf) == 0; +} diff --git a/src/libpsl-native/src/isfile.h b/src/libpsl-native/src/isfile.h new file mode 100644 index 0000000000..53a15f97d3 --- /dev/null +++ b/src/libpsl-native/src/isfile.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" +#include + +PAL_BEGIN_EXTERNC + +bool IsFile(const char* path); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/issymlink.cpp b/src/libpsl-native/src/issymlink.cpp index d4ce011cba..87a8a2e8db 100644 --- a/src/libpsl-native/src/issymlink.cpp +++ b/src/libpsl-native/src/issymlink.cpp @@ -7,13 +7,14 @@ #include #include #include +#include "getlstat.h" #include "issymlink.h" -//! @brief IsSymLink determines if path is a symbolic link +//! @brief IsSymlink determines if path is a symbolic link //! -//! IsSymLink +//! IsSymlink //! -//! @param[in] fileName +//! @param[in] path //! @parblock //! A pointer to the buffer that contains the file name //! @@ -34,63 +35,26 @@ //! - ERROR_INVALID_FUNCTION: incorrect function //! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters //! -//! @retval 1 if path is a symbolic link -//! @retval 0 if path is not a symbolic link -//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! @retval true if path is a symbolic link, false otherwise //! -int32_t IsSymLink(const char* fileName) +bool IsSymLink(const char* path) { - - errno = 0; + errno = 0; // Check parameters - if (!fileName) + if (!path) { errno = ERROR_INVALID_PARAMETER; - return -1; + return false; } - struct stat statBuf; - - int returnCode = lstat(fileName, &statBuf); - - if (returnCode != 0) + struct stat buf; + int32_t ret = GetLStat(path, &buf); + if (ret != 0) { - switch(errno) - { - case EACCES: - errno = ERROR_ACCESS_DENIED; - break; - case EBADF: - errno = ERROR_FILE_NOT_FOUND; - break; - case EFAULT: - errno = ERROR_INVALID_ADDRESS; - break; - case ELOOP: - errno = ERROR_STOPPED_ON_SYMLINK; - break; - case ENAMETOOLONG: - errno = ERROR_GEN_FAILURE; - break; - case ENOENT: - errno = ERROR_FILE_NOT_FOUND; - break; - case ENOMEM: - errno = ERROR_NO_SUCH_USER; - break; - case ENOTDIR: - errno = ERROR_INVALID_NAME; - break; - case EOVERFLOW: - errno = ERROR_BUFFER_OVERFLOW; - break; - default: - errno = ERROR_INVALID_FUNCTION; - } - return -1; + return false; } - return S_ISLNK(statBuf.st_mode) ? 1 : 0; + return S_ISLNK(buf.st_mode); } diff --git a/src/libpsl-native/src/issymlink.h b/src/libpsl-native/src/issymlink.h index 066a4060c1..ebe3733c5a 100644 --- a/src/libpsl-native/src/issymlink.h +++ b/src/libpsl-native/src/issymlink.h @@ -1,9 +1,10 @@ #pragma once #include "pal.h" +#include PAL_BEGIN_EXTERNC -int32_t IsSymLink(const char* fileName); +bool IsSymLink(const char* path); PAL_END_EXTERNC diff --git a/src/libpsl-native/test/CMakeLists.txt b/src/libpsl-native/test/CMakeLists.txt index ed4440d2a3..2f57741b0c 100644 --- a/src/libpsl-native/test/CMakeLists.txt +++ b/src/libpsl-native/test/CMakeLists.txt @@ -9,6 +9,8 @@ add_executable(psl-native-test test-getcomputername.cpp test-getlinkcount.cpp test-getfullyqualifiedname.cpp + test-isdirectory.cpp + test-isfile.cpp test-issymlink.cpp test-isexecutable.cpp test-createsymlink.cpp diff --git a/src/libpsl-native/test/test-createsymlink.cpp b/src/libpsl-native/test/test-createsymlink.cpp index 7bb9997b30..1aab3379a0 100644 --- a/src/libpsl-native/test/test-createsymlink.cpp +++ b/src/libpsl-native/test/test-createsymlink.cpp @@ -39,36 +39,36 @@ protected: EXPECT_TRUE(dir != NULL); // Create symbolic link to file - int ret1 = CreateSymLink(fileSymLink.c_str(), file); - EXPECT_EQ(ret1, 1); + bool ret1 = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_TRUE(ret1); // Create symbolic link to directory - int ret2 = CreateSymLink(dirSymLink.c_str(), dir); - EXPECT_EQ(ret2, 1); + bool ret2 = CreateSymLink(dirSymLink.c_str(), dir); + EXPECT_TRUE(ret2); } ~CreateSymLinkTest() { - int ret; + bool ret; ret = unlink(fileSymLink.c_str()); - EXPECT_EQ(0, ret); + EXPECT_FALSE(ret); ret = unlink(dirSymLink.c_str()); - EXPECT_EQ(0, ret); + EXPECT_FALSE(ret); ret = unlink(file); - EXPECT_EQ(0, ret); + EXPECT_FALSE(ret); ret = rmdir(dir); - EXPECT_EQ(0, ret); + EXPECT_FALSE(ret); } }; TEST_F(CreateSymLinkTest, FilePathNameIsNull) { - int retVal = CreateSymLink(NULL, NULL); - EXPECT_EQ(retVal, 0); + bool retVal = CreateSymLink(NULL, NULL); + EXPECT_FALSE(retVal); EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); } @@ -82,8 +82,8 @@ TEST_F(CreateSymLinkTest, FilePathNameDoesNotExist) unlink(invalidLink.c_str()); // Linux allows creation of symbolic link that points to an invalid file - int retVal = CreateSymLink(invalidLink.c_str(), invalidFile.c_str()); - EXPECT_EQ(retVal, 1); + bool retVal = CreateSymLink(invalidLink.c_str(), invalidFile.c_str()); + EXPECT_TRUE(retVal); std::string target = FollowSymLink(invalidLink.c_str()); EXPECT_EQ(target, invalidFile); @@ -93,8 +93,8 @@ TEST_F(CreateSymLinkTest, FilePathNameDoesNotExist) TEST_F(CreateSymLinkTest, SymLinkToFile) { - int retVal = IsSymLink(fileSymLink.c_str()); - EXPECT_EQ(1, retVal); + bool retVal = IsSymLink(fileSymLink.c_str()); + EXPECT_TRUE(retVal); std::string target = FollowSymLink(fileSymLink.c_str()); char buffer[PATH_MAX]; @@ -104,8 +104,8 @@ TEST_F(CreateSymLinkTest, SymLinkToFile) TEST_F(CreateSymLinkTest, SymLinkToDirectory) { - int retVal = IsSymLink(dirSymLink.c_str()); - EXPECT_EQ(1, retVal); + bool retVal = IsSymLink(dirSymLink.c_str()); + EXPECT_TRUE(retVal); std::string target = FollowSymLink(dirSymLink.c_str()); char buffer[PATH_MAX]; @@ -115,7 +115,7 @@ TEST_F(CreateSymLinkTest, SymLinkToDirectory) TEST_F(CreateSymLinkTest, SymLinkAgain) { - int retVal = CreateSymLink(fileSymLink.c_str(), file); - EXPECT_EQ(0, retVal); + bool retVal = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_FALSE(retVal); EXPECT_EQ(ERROR_FILE_EXISTS, errno); } diff --git a/src/libpsl-native/test/test-isdirectory.cpp b/src/libpsl-native/test/test-isdirectory.cpp new file mode 100644 index 0000000000..27c840cba7 --- /dev/null +++ b/src/libpsl-native/test/test-isdirectory.cpp @@ -0,0 +1,31 @@ +//! @file test-isdirectory.cpp +//! @author Andrew Schwartzmeyer +//! @brief Tests IsDirectory + +#include +#include +#include +#include "isdirectory.h" + +TEST(IsDirectoryTest, RootIsDirectory) +{ + EXPECT_TRUE(IsDirectory("/")); +} + +TEST(IsDirectoryTest, BinLsIsNotDirectory) +{ + EXPECT_FALSE(IsDirectory("/bin/ls")); +} + + +TEST(IsDirectoryTest, ReturnsFalseForFakeDirectory) +{ + EXPECT_FALSE(IsDirectory("SomeMadeUpFileNameThatDoesNotExist")); + EXPECT_EQ(errno, ERROR_FILE_NOT_FOUND); +} + +TEST(IsDirectoryTest, ReturnsFalseForNullInput) +{ + EXPECT_FALSE(IsDirectory(NULL)); + EXPECT_EQ(errno, ERROR_INVALID_PARAMETER); +} diff --git a/src/libpsl-native/test/test-isexecutable.cpp b/src/libpsl-native/test/test-isexecutable.cpp index 36955912ff..d97d7d3159 100644 --- a/src/libpsl-native/test/test-isexecutable.cpp +++ b/src/libpsl-native/test/test-isexecutable.cpp @@ -33,62 +33,52 @@ protected: // First create a file int fd = mkstemp(fileTemplateBuf); EXPECT_TRUE(fd != -1); - file = fileTemplateBuf; + file = fileTemplateBuf; } ~IsExecutableTest() { - int ret; - - ret = unlink(file); - EXPECT_EQ(0, ret); + EXPECT_FALSE(unlink(file)); } void ChangeFilePermission(const char* file, mode_t mode) { - int ret = chmod(file, mode); - EXPECT_EQ(ret, 0); + EXPECT_FALSE(chmod(file, mode)); } }; TEST_F(IsExecutableTest, FilePathNameIsNull) { - int32_t retVal = IsExecutable(NULL); - EXPECT_EQ(retVal, -1); + EXPECT_FALSE(IsExecutable(NULL)); EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); } TEST_F(IsExecutableTest, FilePathNameDoesNotExist) { std::string invalidFile = "/tmp/isexecutabletest_invalidFile"; - int32_t retVal = IsExecutable(invalidFile.c_str()); - EXPECT_EQ(retVal, -1); + EXPECT_FALSE(IsExecutable(invalidFile.c_str())); EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); } TEST_F(IsExecutableTest, NormalFileIsNotIsexecutable) { - int32_t retVal = IsExecutable(file); - EXPECT_EQ(0, retVal); - + EXPECT_FALSE(IsExecutable(file)); + ChangeFilePermission(file, mode_444); - retVal = IsExecutable(file); - EXPECT_EQ(0, retVal); + EXPECT_FALSE(IsExecutable(file)); } TEST_F(IsExecutableTest, FilePermission_700) { ChangeFilePermission(file, mode_700); - int32_t retVal = IsExecutable(file); - EXPECT_EQ(1, retVal); + EXPECT_TRUE(IsExecutable(file)); } TEST_F(IsExecutableTest, FilePermission_777) { ChangeFilePermission(file, mode_777); - int32_t retVal = IsExecutable(file); - EXPECT_EQ(1, retVal); + EXPECT_TRUE(IsExecutable(file)); } diff --git a/src/libpsl-native/test/test-isfile.cpp b/src/libpsl-native/test/test-isfile.cpp new file mode 100644 index 0000000000..09d72f5bfa --- /dev/null +++ b/src/libpsl-native/test/test-isfile.cpp @@ -0,0 +1,30 @@ +//! @file test-isfile.cpp +//! @author Andrew Schwartzmeyer +//! @brief Tests Isfile + +#include +#include +#include +#include "isfile.h" + +TEST(IsFileTest, RootIsFile) +{ + EXPECT_TRUE(IsFile("/")); +} + +TEST(IsFileTest, BinLsIsFile) +{ + EXPECT_TRUE(IsFile("/bin/ls")); +} + +TEST(IsFileTest, CannotGetOwnerOfFakeFile) +{ + EXPECT_FALSE(IsFile("SomeMadeUpFileNameThatDoesNotExist")); + EXPECT_EQ(errno, ERROR_FILE_NOT_FOUND); +} + +TEST(IsFileTest, ReturnsFalseForNullInput) +{ + EXPECT_FALSE(IsFile(NULL)); + EXPECT_EQ(errno, ERROR_INVALID_PARAMETER); +} diff --git a/src/libpsl-native/test/test-issymlink.cpp b/src/libpsl-native/test/test-issymlink.cpp index 4aaa3eb541..512a4053a9 100644 --- a/src/libpsl-native/test/test-issymlink.cpp +++ b/src/libpsl-native/test/test-issymlink.cpp @@ -30,75 +30,60 @@ protected: // First create a file int fd = mkstemp(fileTemplateBuf); EXPECT_TRUE(fd != -1); - file = fileTemplateBuf; + file = fileTemplateBuf; - // Create a temp directory - dir = mkdtemp(dirTemplateBuf); + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); EXPECT_TRUE(dir != NULL); // Create symbolic link to file - int ret1 = symlink(file, fileSymLink.c_str()); - EXPECT_EQ(ret1, 0); - + EXPECT_FALSE(symlink(file, fileSymLink.c_str())); + // Create symbolic link to directory - int ret2 = symlink(dir, dirSymLink.c_str()); - EXPECT_EQ(ret2, 0); + EXPECT_FALSE(symlink(dir, dirSymLink.c_str())); } ~isSymLinkTest() { - int ret; + EXPECT_FALSE(unlink(fileSymLink.c_str())); - ret = unlink(fileSymLink.c_str()); - EXPECT_EQ(0, ret); + EXPECT_FALSE(unlink(dirSymLink.c_str())); - ret = unlink(dirSymLink.c_str()); - EXPECT_EQ(0, ret); + EXPECT_FALSE(unlink(file)); - ret = unlink(file); - EXPECT_EQ(0, ret); - - ret = rmdir(dir); - EXPECT_EQ(0, ret); + EXPECT_FALSE(rmdir(dir)); } }; TEST_F(isSymLinkTest, FilePathNameIsNull) { - int retVal = IsSymLink(NULL); - EXPECT_EQ(retVal, -1); + EXPECT_FALSE(IsSymLink(NULL)); EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); } TEST_F(isSymLinkTest, FilePathNameDoesNotExist) { std::string invalidFile = "/tmp/symlinktest_invalidFile"; - int retVal = IsSymLink(invalidFile.c_str()); - EXPECT_EQ(retVal, -1); + EXPECT_FALSE(IsSymLink(invalidFile.c_str())); EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); } TEST_F(isSymLinkTest, NormalFileIsNotSymLink) { - int retVal = IsSymLink(file); - EXPECT_EQ(0, retVal); + EXPECT_FALSE(IsSymLink(file)); } TEST_F(isSymLinkTest, SymLinkToFile) { - int retVal = IsSymLink(fileSymLink.c_str()); - EXPECT_EQ(1, retVal); + EXPECT_TRUE(IsSymLink(fileSymLink.c_str())); } TEST_F(isSymLinkTest, NormalDirectoryIsNotSymbLink) { - int retVal = IsSymLink(dir); - EXPECT_EQ(0, retVal); + EXPECT_FALSE(IsSymLink(dir)); } TEST_F(isSymLinkTest, SymLinkToDirectory) { - int retVal = IsSymLink(dirSymLink.c_str()); - EXPECT_EQ(1, retVal); + EXPECT_TRUE(IsSymLink(dirSymLink.c_str())); } - diff --git a/test/csharp/test_CorePsPlatform.cs b/test/csharp/test_CorePsPlatform.cs index d0132b121d..80d3878918 100644 --- a/test/csharp/test_CorePsPlatform.cs +++ b/test/csharp/test_CorePsPlatform.cs @@ -15,30 +15,6 @@ namespace PSTests Assert.True(Platform.IsCore); } - [Fact] - public static void TestHasCom() - { - Assert.False(Platform.HasCom()); - } - - [Fact] - public static void TestHasAmsi() - { - Assert.False(Platform.HasAmsi()); - } - - [Fact] - public static void TestHasDriveAutoMounting() - { - Assert.False(Platform.HasDriveAutoMounting()); - } - - [Fact] - public static void TestHasRegistrySupport() - { - Assert.False(Platform.HasRegistrySupport()); - } - [Fact] public static void TestGetUserName() { diff --git a/test/powershell/Modules/CimCmdlets/Get-CimClass.Tests.ps1 b/test/powershell/Modules/CimCmdlets/Get-CimClass.Tests.ps1 new file mode 100644 index 0000000000..6624c878a3 --- /dev/null +++ b/test/powershell/Modules/CimCmdlets/Get-CimClass.Tests.ps1 @@ -0,0 +1,6 @@ +Describe 'Get-CimClass' { + # Get-CimClass works only on windows + It 'can get CIM_Error CIM class' -Skip:(-not $IsWindows) { + Get-CimClass -ClassName CIM_Error | Should Not Be $null + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 index 593f892ad9..155f2226f0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 @@ -2,17 +2,17 @@ function Clean-State { if (Test-Path $FullyQualifiedLink) { - Remove-Item $FullyQualifiedLink -Force + Remove-Item $FullyQualifiedLink -Force } if (Test-Path $FullyQualifiedFile) { - Remove-Item $FullyQualifiedFile -Force + Remove-Item $FullyQualifiedFile -Force } if (Test-Path $FullyQualifiedFolder) { - Remove-Item $FullyQualifiedFolder -Force + Remove-Item $FullyQualifiedFolder -Force } } @@ -26,17 +26,17 @@ Describe "New-Item" { $FullyQualifiedLink = Join-Path -Path $tmpDirectory -ChildPath $testlink BeforeEach { - Clean-State + Clean-State } It "should call the function without error" { - { New-Item -Name $testfile -Path $tmpDirectory -ItemType file } | Should Not Throw + { New-Item -Name $testfile -Path $tmpDirectory -ItemType file } | Should Not Throw } It "Should create a file without error" { - New-Item -Name $testfile -Path $tmpDirectory -ItemType file + New-Item -Name $testfile -Path $tmpDirectory -ItemType file - Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true $fileInfo = Get-ChildItem $FullyQualifiedFile $fileInfo.Target | Should Be $null @@ -44,72 +44,72 @@ Describe "New-Item" { } It "Should create a folder without an error" { - New-Item -Name newDirectory -Path $tmpDirectory -ItemType directory + New-Item -Name newDirectory -Path $tmpDirectory -ItemType directory - Test-Path $FullyQualifiedFolder | Should Be $true + Test-Path $FullyQualifiedFolder | Should Be $true } It "Should create a file using the ni alias" { - ni -Name $testfile -Path $tmpDirectory -ItemType file + ni -Name $testfile -Path $tmpDirectory -ItemType file - Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true } It "Should create a file using the Type alias instead of ItemType" { - New-Item -Name $testfile -Path $tmpDirectory -Type file + New-Item -Name $testfile -Path $tmpDirectory -Type file - Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true } It "Should create a file with sample text inside the file using the Value switch" { - $expected = "This is test string" - New-Item -Name $testfile -Path $tmpDirectory -ItemType file -Value $expected + $expected = "This is test string" + New-Item -Name $testfile -Path $tmpDirectory -ItemType file -Value $expected - Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true - Get-Content $FullyQualifiedFile | Should Be $expected + Get-Content $FullyQualifiedFile | Should Be $expected } It "Should not create a file when the Name switch is not used and only a directory specified" { - #errorAction used because permissions issue in Windows - New-Item -Path $tmpDirectory -ItemType file -ErrorAction SilentlyContinue + #errorAction used because permissions issue in Windows + New-Item -Path $tmpDirectory -ItemType file -ErrorAction SilentlyContinue - Test-Path $FullyQualifiedFile | Should Be $false + Test-Path $FullyQualifiedFile | Should Be $false } It "Should create a file when the Name switch is not used but a fully qualified path is specified" { - New-Item -Path $FullyQualifiedFile -ItemType file + New-Item -Path $FullyQualifiedFile -ItemType file - Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true } It "Should be able to create a multiple items in different directories" { - $FullyQualifiedFile2 = Join-Path -Path $tmpDirectory -ChildPath test2.txt - New-Item -ItemType file -Path $FullyQualifiedFile, $FullyQualifiedFile2 + $FullyQualifiedFile2 = Join-Path -Path $tmpDirectory -ChildPath test2.txt + New-Item -ItemType file -Path $FullyQualifiedFile, $FullyQualifiedFile2 - Test-Path $FullyQualifiedFile | Should Be $true - Test-Path $FullyQualifiedFile2 | Should Be $true + Test-Path $FullyQualifiedFile | Should Be $true + Test-Path $FullyQualifiedFile2 | Should Be $true - Remove-Item $FullyQualifiedFile2 + Remove-Item $FullyQualifiedFile2 } It "Should be able to call the whatif switch without error" { - { New-Item -Name testfile.txt -Path $tmpDirectory -ItemType file -WhatIf } | Should Not Throw + { New-Item -Name testfile.txt -Path $tmpDirectory -ItemType file -WhatIf } | Should Not Throw } It "Should not create a new file when the whatif switch is used" { - New-Item -Name $testfile -Path $tmpDirectory -ItemType file -WhatIf + New-Item -Name $testfile -Path $tmpDirectory -ItemType file -WhatIf - Test-Path $FullyQualifiedFile | Should Be $false + Test-Path $FullyQualifiedFile | Should Be $false } It "Should create a symbolic link of a file without error" { - New-Item -Name $testfile -Path $tmpDirectory -ItemType file - Test-Path $FullyQualifiedFile | Should Be $true + New-Item -Name $testfile -Path $tmpDirectory -ItemType file + Test-Path $FullyQualifiedFile | Should Be $true - New-Item -ItemType SymbolicLink -Target $FullyQualifiedFile -Name $testlink -Path $tmpDirectory - Test-Path $FullyQualifiedLink | Should Be $true + New-Item -ItemType SymbolicLink -Target $FullyQualifiedFile -Name $testlink -Path $tmpDirectory + Test-Path $FullyQualifiedLink | Should Be $true $fileInfo = Get-ChildItem $FullyQualifiedLink $fileInfo.Target | Should Match ([regex]::Escape($FullyQualifiedFile)) @@ -128,18 +128,18 @@ Describe "New-Item" { } It "Should create a symbolic link from directory without error" { - New-Item -Name $testFolder -Path $tmpDirectory -ItemType directory - Test-Path $FullyQualifiedFolder | Should Be $true + New-Item -Name $testFolder -Path $tmpDirectory -ItemType directory + Test-Path $FullyQualifiedFolder | Should Be $true - New-Item -ItemType SymbolicLink -Target $FullyQualifiedFolder -Name $testlink -Path $tmpDirectory - Test-Path $FullyQualifiedLink | Should Be $true + New-Item -ItemType SymbolicLink -Target $FullyQualifiedFolder -Name $testlink -Path $tmpDirectory + Test-Path $FullyQualifiedLink | Should Be $true $fileInfo = Get-ChildItem $FullyQualifiedLink $fileInfo.Target | Should Match ([regex]::Escape($FullyQualifiedFolder)) $fileInfo.LinkType | Should Be "SymbolicLink" - # Remove the link explicitly to avoid broken symlink issue - Remove-Item $FullyQualifiedLink -Force + # Remove the link explicitly to avoid broken symlink issue + Remove-Item $FullyQualifiedLink -Force } It "Should create a hard link of a file without error" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 index b5631218d5..cdc5a239cc 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1 @@ -1,24 +1,24 @@ Describe "ExecutionPolicy" { Context "Check Get-ExecutionPolicy behavior" { - It "Should throw PlatformNotSupported when not on Windows" -Skip:$IsWindows { - { Get-ExecutionPolicy } | Should Throw "Operation is not supported on this platform." - } + It "Should unrestricted when not on Windows" -Skip:$IsWindows { + Get-ExecutionPolicy | Should Be Unrestricted + } - It "Should return Microsoft.Powershell.ExecutionPolicy PSObject on Windows" -Skip:($IsLinux -Or $IsOSX) { - (Get-ExecutionPolicy).GetType() | Should Be Microsoft.Powershell.ExecutionPolicy - } + It "Should return Microsoft.Powershell.ExecutionPolicy PSObject on Windows" -Skip:($IsLinux -Or $IsOSX) { + (Get-ExecutionPolicy).GetType() | Should Be Microsoft.Powershell.ExecutionPolicy + } } Context "Check Set-ExecutionPolicy behavior" { - It "Should throw PlatformNotSupported when not on Windows" -Skip:$IsWindows { - { Set-ExecutionPolicy Unrestricted } | Should Throw "Operation is not supported on this platform." - } + It "Should throw PlatformNotSupported when not on Windows" -Skip:$IsWindows { + { Set-ExecutionPolicy Unrestricted } | Should Throw "Operation is not supported on this platform." + } - It "Should succeed on Windows" -Skip:($IsLinux -Or $IsOSX) { - # We use the Process scope to avoid affecting the system - # Unrestricted is assumed "safe", otherwise these tests would not be running - { Set-ExecutionPolicy -Force -Scope Process -ExecutionPolicy Unrestricted } | Should Not Throw - } + It "Should succeed on Windows" -Skip:($IsLinux -Or $IsOSX) { + # We use the Process scope to avoid affecting the system + # Unrestricted is assumed "safe", otherwise these tests would not be running + { Set-ExecutionPolicy -Force -Scope Process -ExecutionPolicy Unrestricted } | Should Not Throw + } } } diff --git a/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 b/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 index 7821fca3ee..0173b91695 100644 --- a/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 +++ b/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 @@ -13,6 +13,16 @@ Describe "PSReadLine" { $module.Version | Should Be "1.2" } + It "Should use Emacs Bindings on Linux and OS X" -skip:$IsWindows { + (Get-PSReadLineOption).EditMode | Should Be Emacs + (Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be BeginningOfLine + } + + It "Should use Windows Bindings on Windows" -skip:(-not $IsWindows) { + (Get-PSReadLineOption).EditMode | Should Be Windows + (Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be SelectAll + } + It "Should set the edit mode" { Set-PSReadlineOption -EditMode Windows (Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be SelectAll diff --git a/test/powershell/enginecore/Parser.Test.ps1 b/test/powershell/enginecore/Parser.Test.ps1 new file mode 100644 index 0000000000..d216989a64 --- /dev/null +++ b/test/powershell/enginecore/Parser.Test.ps1 @@ -0,0 +1,753 @@ +Describe "ParserTests (admin\monad\tests\monad\src\engine\core\ParserTests.cs)" { + BeforeAll { + $functionDefinitionFile = Join-Path -Path $TestDrive -ChildPath "functionDefinition.ps1" + $functionDefinition = @' + function testcmd-parserbvt + { + [CmdletBinding()] + param ( + [Parameter(Position = 0)] + [string] $Property1 = "unset", + + [Parameter(Position = 1)] + [string] $Property2 = "unset", + + [Parameter(Position = 2)] + [string] $Property3 = "unset", + + [Parameter()] + [ValidateSet("default","array","object","nestedobject","struct","mshobject","nulltostring")] + [string]$ReturnType = "default" + ) + + BEGIN {} + + PROCESS { + if ( ! $ReturnType ) { + $ReturnType = "default" + } + + switch ( $ReturnType ) + { + "default" { + $result = "$Property1;$Property2;$Property3" + break + } + "array" { + $result = 1,2,3 + break + } + "object" { + $result = new-object psobject + break + } + "nestedobject" { + $result = [pscustomobject]@{Name="John";Person=[pscustomobject]@{Name="John";Age=30}} + break + } + "struct" { + $result = [pscustomobject]@{Name="John";Age=30} + break + } + "mshobject" { + $result = new-object psobject + break + } + "nulltostring" { + $result = $null + break + } + default { + throw ([invalidoperationexception]::new("ReturnType parameter wasn't of any Expected value!")) + break + } + } + return $result + } + + END {} + } +'@ + $functionDefinition>$functionDefinitionFile + + $PowerShell = [powershell]::Create() + function ExecuteCommand { + param ([string]$command) + try { + $PowerShell.AddScript(". $functionDefinitionFile").Invoke() + $PowerShell.AddScript($command).Invoke() + } + finally { + $PowerShell.Commands.Clear() + } + } + } + BeforeEach { + $testfile = Join-Path -Path $TestDrive -ChildPath "testfile.ps1" + $shellfile = Join-Path -Path $TestDrive -ChildPath "testfile.cmd" + $testfolder1 = Join-Path -Path $TestDrive -ChildPath "dir1" + $testfolder2 = Join-Path -Path $testfolder1 -ChildPath "dir2" + if(-not(Test-Path $testfolder1)) + { + New-Item $testfolder1 -Type Directory + } + if(-not(Test-Path $testfolder2)) + { + New-Item $testfolder2 -Type Directory + } + $testdirfile1 = Join-Path -Path $testfolder2 -ChildPath "testdirfile1.txt" + $testdirfile2 = Join-Path -Path $testfolder2 -ChildPath "testdirfile2.txt" + "">$testdirfile1 + "">$testdirfile2 + } + AfterEach { + $PowerShell.Commands.Clear() + if(Test-Path $testfile) + { + Remove-Item $testfile + } + if(Test-Path $shellfile) + { + Remove-Item $shellfile + } + if(Test-Path $testdirfile1) + { + Remove-Item $testdirfile1 + } + if(Test-Path $testdirfile2) + { + Remove-Item $testdirfile2 + } + if(Test-Path $testfolder2) + { + Remove-Item $testfolder2 + } + if(Test-Path $testfolder1) + { + Remove-Item $testfolder1 + } + } + + AfterAll { + if(Test-Path $functionDefinitionFile) + { + Remove-Item $functionDefinitionFile + } + } + + It "Throws a syntax error when parsing a string without a closing quote. (line 164)" { + try { + ExecuteCommand '"This is a test' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "IncompleteParseException" + } + } + + It "Throws an error if an open parenthesis is not closed (line 176)" { + try { + ExecuteCommand "(" + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | should be "IncompleteParseException" + } + } + + It "Throws an exception if the the first statement starts with an empty pipe element (line 188)" { + try { + ExecuteCommand "| get-location" + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | should be "ParseException" + } + } + + It "Throws an CommandNotFoundException exception if using a label in front of an if statement is not allowed. (line 225)"{ + ExecuteCommand ":foo if ($x -eq 3) { 1 }" + $PowerShell.HadErrors | should be $true + $PowerShell.Streams.Error.FullyQualifiedErrorId | should be "CommandNotFoundException" + } + + It "Pipe an expression into a value expression. (line 237)" { + try { + ExecuteCommand "testcmd-parserbvt | 3" + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | should be "ParseException" + } + + try { + ExecuteCommand "testcmd-parserbvt | $(1 + 1)" + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | should be "ParseException" + } + + try { + ExecuteCommand "testcmd-parserbvt | 'abc'" + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | should be "ParseException" + } + } + + It "Throws when you pipe into a value expression (line 238)" { + foreach($command in "1;2;3|3",'1;2;3|$(1+1)',"1;2;3|'abc'") { + try { + ExecuteCommand $command + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + } + + It "Throws an incomplete parse exeception when a comma follows an expression (line 247)" { + try { + ExecuteCommand "(1+ 1)," + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "IncompleteParseException" + } + } + + + It "Test that invoke has a higher precedence for a script than for an executable. (line 279)" { + "1">$testfile + $result = ExecuteCommand ". $testfile" + $result | should be 1 + } + + It "This test will check that a path is correctly interpreted when using '..' and '.' (line 364)" { + $result = ExecuteCommand "set-location $TestDrive; get-childitem dir1\.\.\.\..\dir1\.\dir2\..\..\dir1\.\dir2" + $result.Count | should be 2 + $result[0].Name | should be "testdirfile1.txt" + $result[1].Name | should be "testdirfile2.txt" + } + + It "This test will check that the parser can handle a mix of forward slashes and back slashes in the path (line 417)" { + $result = ExecuteCommand "get-childitem $TestDrive/dir1/./.\.\../dir1/.\dir2\../..\dir1\.\dir2" + $result.Count | should be 2 + $result[0].Name | should be "testdirfile1.txt" + $result[1].Name | should be "testdirfile2.txt" + } + + It "This test checks that the asterisk globs as expected. (line 545)" { + $result = ExecuteCommand "get-childitem $TestDrive/dir1\dir2\*.txt" + $result.Count | should be 2 + $result[0].Name | should be "testdirfile1.txt" + $result[1].Name | should be "testdirfile2.txt" + } + + It "This test checks that we can use a range for globbing: [1-2] (line 557)" { + $result = ExecuteCommand "get-childitem $TestDrive/dir1\dir2\testdirfile[1-2].txt" + $result.Count | should be 2 + $result[0].Name | should be "testdirfile1.txt" + $result[1].Name | should be "testdirfile2.txt" + } + + It "This test will check that escaping the $ sigil inside single quotes simply returns the $ character. (line 583)" { + $result = ExecuteCommand "'`$'" + $result | should be "`$" + } + + It "Test that escaping a space just returns that space. (line 593)" { + $result = ExecuteCommand '"foo` bar"' + $result | should be "foo bar" + } + + It "Test that escaping any character with no special meaning just returns that char. (line 602)" { + $result = ExecuteCommand '"fo`obar"' + $result | should be "foobar" + } + + Context "Test that we support all of the C# escape sequences. We use the ` instead of \. (line 613)" { + # the first two sequences are tricky, because we need to provide something to + # execute without causing an incomplete parse error + $tests = @{ sequence = "write-output ""`'"""; expected = ([char]39) }, + @{ sequence = 'write-output "`""'; expected = ([char]34) }, + # this is a string, of 2 "\", the initial backtick should essentially be ignored + @{ sequence = '"`\\"'; expected = '\\' }, + # control sequences + @{ sequence = '"`0"'; expected = ([char]0) }, # null + @{ sequence = '"`a"'; expected = ([char]7) }, + @{ sequence = '"`b"'; expected = ([char]8) }, # backspace + @{ sequence = '"`f"'; expected = ([char]12) }, # form + @{ sequence = '"`n"'; expected = ([char]10) }, # newline + @{ sequence = '"`r"'; expected = ([char]13) }, # return + @{ sequence = '"`t"'; expected = ([char]9) }, # tab + @{ sequence = '"`v"'; expected = ([char]11) } + It "C# escape sequence is supported using `` instead of \. (line 613)" -TestCases $tests { + param ( $sequence, $expected ) + $result = ExecuteCommand $sequence + $result | should be $expected + } + } + + + It "This test checks that array substitution occurs inside double quotes. (line 646)" { + $result = ExecuteCommand '$MyArray = "a","b";"Hello $MyArray"' + $result | should be "Hello a b" + } + + It "This tests declaring an array in nested variable tables. (line 761)" { + $result = ExecuteCommand "`$Variable:vtbl1:vtbl2:b=@(5,6);`$Variable:vtbl1:vtbl2:b" + $result.Count | should be 2 + $result[0] | should be 5 + $result[1] | should be 6 + } + + It "Test a simple multiple assignment. (line 773)" { + $result = ExecuteCommand '$one,$two = 1,2,3; "One = $one"; "Two = $two"' + $result.Count | should be 2 + $result[0] | should be "One = 1" + $result[1] | should be "Two = 2 3" + } + + It "Tests script, global and local scopes from a function inside a script. (line 824)" { + "`$var = 'script';function func { `$var; `$var = 'local'; `$local:var; `$script:var; `$global:var };func;`$var;">$testfile + ExecuteCommand "`$var = 'global'" + $result = ExecuteCommand "$testfile" + $result.Count | should be 5 + $result[0] | should be "script" + $result[1] | should be "local" + $result[2] | should be "script" + $result[3] | should be "global" + $result[4] | should be "script" + } + + It "Use break inside of a loop that is inside another loop. (line 945)" { + $commands = " while (1) { 1; while(1) { 2; break; 3; }; 4; break; 5; } ", + " for (;;) { 1; for(;;) { 2; break; 3; }; 4; break; 5; } ", + " foreach(`$a in 1,2,3) { 1; foreach( `$b in 1,2,3 ) { 2; break; 3; }; 4; break; 5; } " + $results = "1", "2", "4" + $i = 0 + for(;$i -lt $commands.Count;$i++) + { + $result = ExecuteCommand $commands[$i] + $result | should be $results + } + } + + It "Use break in two loops with same label. (line 967)" { + $commands = " :foo while (1) { 1; :foo while(1) { 2; break foo; 3; }; 4; break; 5; } ", + " :foo for (;;) { 1; :foo for(;;) { 2; break foo; 3; }; 4; break; 5; } ", + " :foo foreach(`$a in 1,2,3) { 1; :foo foreach( `$b in 1,2,3 ) { 2; break foo; 3; }; 4; break; 5; } " + $results = "1", "2", "4" + $i = 0 + for(;$i -lt $commands.Count;$i++) + { + $result = ExecuteCommand $commands[$i] + $result | should be $results + } + } + + It "Try continue inside of different loop statements. (line 1039)" { + $commands = " `$a = 0; while (`$a -lt 2) { `$a; `$a += 1; continue; 2; } ", + " for (`$a = 0;`$a -lt 2; `$a += 1) { 9; continue; 3; } ", + " foreach(`$a in 0,1) { `$a; continue; 2; } " + $result = ExecuteCommand $commands[0] + $result | should be "0", "1" + $result = ExecuteCommand $commands[1] + $result | should be "9", "9" + $result = ExecuteCommand $commands[2] + $result | should be "0", "1" + } + + It "Use a label to continue an inner loop. (line 1059)" { + $commands = " `$x = 0; while (`$x -lt 1) { `$x += 1; `$x; `$a = 0; :foo while(`$a -lt 2) { `$a += 1; `$a; continue foo; 3; }; 4; continue; 5; } ", + " for (`$x = 0;`$x -lt 1;`$x += 1) { 1; :foo for(`$a = 0; `$a -lt 2; `$a += 1) { `$a; continue foo; 3; }; 4; continue; 5; } ", + " foreach(`$a in 1) { 1; :foo foreach( `$b in 1,2 ) { `$b; continue foo; 3; }; 4; continue; 5; } " + $result = ExecuteCommand $commands[0] + $result | should be "1", "1", "2", "4" + $result = ExecuteCommand $commands[1] + $result | should be "1", "0", "1", "4" + $result = ExecuteCommand $commands[2] + $result | should be "1", "1", "2", "4" + } + + It "Use continue with a label on a nested loop. (line 1059)" { + $commands = " `$x = 0; :foo while (`$x -lt 2) { `$x; `$x += 1; :bar while(1) { 2; continue foo; 3; }; 4; continue; 5; } ", + " :foo for (`$x = 0;`$x -lt 2;`$x += 1) { 1; :bar for(;;) { 2; continue foo; 3; }; 4; continue; 5; } ", + " :foo foreach(`$a in 1,2) { 1; :bar foreach( `$b in 1,2,3 ) { 2; continue foo; 3; }; 4; continue; 5; } " + $result = ExecuteCommand $commands[0] + $result | should be "0", "2", "1", "2" + $result = ExecuteCommand $commands[1] + $result | should be "1", "2", "1", "2" + $result = ExecuteCommand $commands[2] + $result | should be "1", "2", "1", "2" + } + + It "This test will check that it is a syntax error to use if without a code block. (line 1141)" { + try { + ExecuteCommand 'if ("true")' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "IncompleteParseException" + } + } + + It "This test will check that it is a syntax error if the if condition is not complete. (line 1150)" { + try { + ExecuteCommand 'if (' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "IncompleteParseException" + } + } + + It "This test will check that it is a syntax error to have an if condition without parentheses. (line 1159)" { + try { + ExecuteCommand 'if "true" { 1} else {2}' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + + It "This test will check that the parser throws a syntax error when the if condition is missing the closing parentheses. (line 1168)" { + try { + ExecuteCommand 'if ("true" { 1};' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + + It "This test will check that it is a syntax error to have an else keyword without the corresponding code block. (line 1177)" { + try { + ExecuteCommand 'if ("true") {1} else' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "IncompleteParseException" + } + } + + It "This test will check that the parser throws a syntax error when a foreach loop is not complete. (line 1238)" { + try { + ExecuteCommand '$count=0;$files = $(get-childitem / -filter *.txt );foreach ($i ;$count' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + + It "This test will check that the parser throws a syntax error if the foreach loop is not complete. (line 1248)" { + try { + ExecuteCommand '$count=0;$files = $(get-childitem / -filter *.txt );foreach ($i in ;$count' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + + It "This will test that the parser throws a syntax error if the foreach loop is missing a closing parentheses. (line 1258)" { + try { + ExecuteCommand '$count=0;$files = $(get-childitem / -filter *.txt );foreach ($i in $files ;$count' + throw "Execution OK" + } + catch { + $_.FullyQualifiedErrorId | Should be "ParseException" + } + } + + It "Test that if an exception is thrown from the try block it will be caught in the apprpropriate catch block and that the finally block will run regardless of whether an exception is thrown. (line 1317)" { + $result = ExecuteCommand 'try { try { throw (new-object System.ArgumentException) } catch [System.DivideByZeroException] { } finally { "Finally" } } catch { $_.Exception.GetType().FullName }' + $result | should be "Finally", "System.ArgumentException" + } + + It "Test that null can be passed to a method that expects a reference type. (line 1439)" { + $result = ExecuteCommand '$test = "String";$test.CompareTo($())' + $result | should be 1 + } + + It "Tests that command expansion operators can be used as a parameter to an object method. (line 1507)" { + $result = ExecuteCommand '$test = "String";$test.SubString($("hello" | foreach-object { $_.length - 2 } ))' + $result | should be "ing" + } + + It "Test that & can be used as a parameter as long as it is quoted. (line 1606)" { + $result = ExecuteCommand 'testcmd-parserbvt `&get-childitem' + $result | should be "&get-childitem;unset;unset" + $result = ExecuteCommand 'testcmd-parserbvt `&*' + $result | should be "&*;unset;unset" + } + + It "Run a command with parameters. (line 1621)" { + $result = ExecuteCommand 'testcmd-parserBVT -Property1 set' + $result | should be "set;unset;unset" + } + + It "Test that typing a number at the command line will return that number. (line 1630)" { + $result = ExecuteCommand '3' + $result | should be "3" + $result.gettype() |should be ([int]) + } + + It "This test will check that an msh script can be run without invoking. (line 1641)" { + "1">$testfile + $result = ExecuteCommand ". $testfile" + $result | should be 1 + } + + It "Test that an alias is resolved before a function. (line 1657)" { + $result = ExecuteCommand 'set-alias parserInvokeTest testcmd-parserBVT;function parserInvokeTest { 3 };parserInvokeTest' + $result | should be "unset;unset;unset" + } + + It "Test that functions are resolved before cmdlets. (line 1678)"{ + $result = ExecuteCommand 'function testcmd-parserBVT { 3 };testcmd-parserBVT' + $result | should be "3" + } + + It "Check that a command that uses shell execute can be run from the command line and that no exception is thrown. (line 1702)" { + if ( $IsLinux -or $IsOSX ) { + # because we execute on *nix based on executable bit, and the file name doesn't matter + # so we can use the same filename as for windows, just make sure it's executable with chmod + "#!/bin/sh`necho ""Hello World""" | out-file -encoding ASCII $shellfile + /bin/chmod +x $shellfile + } + else { + "@echo Hello, I'm a Cmd script!">$shellfile + } + { ExecuteCommand "$shellfile" } | Should Not Throw + } + + Context "Boolean Tests (starting at line 1723 to line 1772)" { + $testData = @( + @{ Script = '"False"'; Expected = $true } + @{ Script = 'if ("A") { $true } else { $false }'; Expected = $true } + @{ Script = 'if (" ") { $true } else { $false }'; Expected = $true } + @{ Script = 'if ("String with spaces") { $true } else { $false }'; Expected = $true } + @{ Script = 'if ("DoubleQuoted") { $true } else { $false }'; Expected = $true } + @{ Script = 'if ("StringWithNullVar$aEmbedded") { $true } else { $false }'; Expected = $true } + @{ Script = 'if (0) { $true } else { $false }'; Expected = $false } + @{ Script = '$a = $(0);if ($a) { $true } else { $false }'; Expected = $false } + @{ Script = '$obj = testcmd-parserBVT -ReturnType object;if ($obj) { $true } else { $false }'; Expected = $true } + ) + It "