patch 8.2.3071: shell options are not set properly for PowerShell

Problem:    Shell options are not set properly for PowerShell.
Solution:   Use better option defaults. (Mike Willams, closes #8459)
diff --git a/src/fileio.c b/src/fileio.c
index 4bd773e..55012bf 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -5245,9 +5245,10 @@
 
     // Backslashes in a temp file name cause problems when filtering with
     // "sh".  NOTE: This also checks 'shellcmdflag' to help those people who
-    // didn't set 'shellslash'.
+    // didn't set 'shellslash' but only if not using PowerShell.
     retval = utf16_to_enc(itmp, NULL);
-    if (*p_shcf == '-' || p_ssl)
+    if ((strstr((char *)gettail(p_sh), "powershell") == NULL
+						&& *p_shcf == '-') || p_ssl)
 	for (p = retval; *p; ++p)
 	    if (*p == '\\')
 		*p = '/';
diff --git a/src/misc2.c b/src/misc2.c
index 0553c2c..6bbfbd7 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1396,7 +1396,9 @@
 /*
  * Escape "string" for use as a shell argument with system().
  * This uses single quotes, except when we know we need to use double quotes
- * (MS-DOS and MS-Windows without 'shellslash' set).
+ * (MS-DOS and MS-Windows not using PowerShell and without 'shellslash' set).
+ * PowerShell also uses a novel escaping for enclosed single quotes - double
+ * them up.
  * Escape a newline, depending on the 'shell' option.
  * When "do_special" is TRUE also replace "!", "%", "#" and things starting
  * with "<" like "<cfile>".
@@ -1412,6 +1414,10 @@
     char_u	*escaped_string;
     int		l;
     int		csh_like;
+# ifdef MSWIN
+    int		powershell;
+    int		double_quotes;
+# endif
 
     // Only csh and similar shells expand '!' within single quotes.  For sh and
     // the like we must not put a backslash before it, it will be taken
@@ -1419,12 +1425,18 @@
     // Csh also needs to have "\n" escaped twice when do_special is set.
     csh_like = csh_like_shell();
 
+# ifdef MSWIN
+    // PowerShell only accepts single quotes so override p_ssl.
+    powershell = strstr((char *)gettail(p_sh), "powershell") != NULL;
+    double_quotes = !powershell && !p_ssl;
+# endif
+
     // First count the number of extra bytes required.
     length = (unsigned)STRLEN(string) + 3;  // two quotes and a trailing NUL
     for (p = string; *p != NUL; MB_PTR_ADV(p))
     {
 # ifdef MSWIN
-	if (!p_ssl)
+	if (double_quotes)
 	{
 	    if (*p == '"')
 		++length;		// " -> ""
@@ -1432,7 +1444,14 @@
 	else
 # endif
 	if (*p == '\'')
-	    length += 3;		// ' => '\''
+	{
+# ifdef MSWIN
+	    if (powershell)
+		length +=2;		// ' => ''
+	    else
+# endif
+		length += 3;		// ' => '\''
+	}
 	if ((*p == '\n' && (csh_like || do_newline))
 		|| (*p == '!' && (csh_like || do_special)))
 	{
@@ -1455,7 +1474,7 @@
 
 	// add opening quote
 # ifdef MSWIN
-	if (!p_ssl)
+	if (double_quotes)
 	    *d++ = '"';
 	else
 # endif
@@ -1464,7 +1483,7 @@
 	for (p = string; *p != NUL; )
 	{
 # ifdef MSWIN
-	    if (!p_ssl)
+	    if (double_quotes)
 	    {
 		if (*p == '"')
 		{
@@ -1478,10 +1497,20 @@
 # endif
 	    if (*p == '\'')
 	    {
-		*d++ = '\'';
-		*d++ = '\\';
-		*d++ = '\'';
-		*d++ = '\'';
+# ifdef MSWIN
+		if (powershell)
+		{
+		    *d++ = '\'';
+		    *d++ = '\'';
+		}
+		else
+# endif
+		{
+		    *d++ = '\'';
+		    *d++ = '\\';
+		    *d++ = '\'';
+		    *d++ = '\'';
+		}
 		++p;
 		continue;
 	    }
@@ -1507,7 +1536,7 @@
 
 	// add terminating quote and finish with a NUL
 # ifdef MSWIN
-	if (!p_ssl)
+	if (double_quotes)
 	    *d++ = '"';
 	else
 # endif
diff --git a/src/option.c b/src/option.c
index 07bb71e..5fc059e 100644
--- a/src/option.c
+++ b/src/option.c
@@ -932,6 +932,27 @@
 		options[idx_srr].def_val[VI_DEFAULT] = p_srr;
 	    }
 	}
+# ifdef MSWIN
+	// PowerShell 5.1/.NET outputs UTF-16 with BOM so re-encode to the
+	// current codepage
+	else if (   fnamecmp(p, "powershell") == 0
+		    || fnamecmp(p, "powershell.exe") == 0
+		)
+	{
+# if defined(FEAT_QUICKFIX)
+		if (do_sp)
+		{
+		    p_sp = (char_u *)"2>&1 | Out-File -Encoding default";
+		    options[idx_sp].def_val[VI_DEFAULT] = p_sp;
+		}
+# endif
+		if (do_srr)
+		{
+		    p_srr = (char_u *)"2>&1 | Out-File -Encoding default";
+		    options[idx_srr].def_val[VI_DEFAULT] = p_srr;
+		}
+	}
+#endif
 	else
 	    // Always use POSIX shell style redirection if we reach this
 	    if (       fnamecmp(p, "sh") == 0
@@ -984,11 +1005,35 @@
      * Set 'shellcmdflag', 'shellxquote', and 'shellquote' depending on the
      * 'shell' option.
      * This is done after other initializations, where 'shell' might have been
-     * set, but only if they have not been set before.  Default for p_shcf is
-     * "/c", for p_shq is "".  For "sh" like  shells it is changed here to
-     * "-c" and "\"".  And for Win32 we need to set p_sxq instead.
+     * set, but only if they have not been set before.
+     * Default values depend on shell (cmd.exe is default shell):
+     *
+     *			    p_shcf	p_sxq
+     * cmd.exe          -   "/c"	"("
+     * powershell.exe   -   "-Command"	"\""
+     * "sh" like shells -   "-c"	"\""
+     *
+     * For Win32 p_sxq is set instead of p_shq to include shell redirection.
      */
-    if (strstr((char *)gettail(p_sh), "sh") != NULL)
+    if (strstr((char *)gettail(p_sh), "powershell") != NULL)
+    {
+	int	idx_opt;
+
+	idx_opt = findoption((char_u *)"shcf");
+	if (idx_opt >= 0 && !(options[idx_opt].flags & P_WAS_SET))
+	{
+	    p_shcf = (char_u*)"-Command";
+	    options[idx_opt].def_val[VI_DEFAULT] = p_shcf;
+	}
+
+	idx_opt = findoption((char_u *)"sxq");
+	if (idx_opt >= 0 && !(options[idx_opt].flags & P_WAS_SET))
+	{
+	    p_sxq = (char_u*)"\"";
+	    options[idx_opt].def_val[VI_DEFAULT] = p_sxq;
+	}
+    }
+    else if (strstr((char *)gettail(p_sh), "sh") != NULL)
     {
 	int	idx3;
 
diff --git a/src/os_win32.c b/src/os_win32.c
index a966c53..1a005c9 100644
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -2142,7 +2142,8 @@
 	return FALSE;
 
     // Using the name directly when a Unix-shell like 'shell'.
-    if (strstr((char *)gettail(p_sh), "sh") != NULL)
+    if (strstr((char *)gettail(p_sh), "powershell") == NULL
+				&& strstr((char *)gettail(p_sh), "sh") != NULL)
 	noext = TRUE;
 
     if (use_pathext)
diff --git a/src/testdir/test_shell.vim b/src/testdir/test_shell.vim
index f992b8e..753a0d5 100644
--- a/src/testdir/test_shell.vim
+++ b/src/testdir/test_shell.vim
@@ -24,8 +24,10 @@
   if has('win32')
     let shells += [['cmd', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', ''],
           \ ['cmd.exe', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '('],
-          \ ['powershell.exe', '-c', '>', '', '>', '"&|<>()@^', '"'],
-          \ ['powershell', '-c', '>', '', '>', '"&|<>()@^', '"'],
+          \ ['powershell.exe', '-Command', '2>&1 | Out-File -Encoding default',
+          \           '', '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
+          \ ['powershell', '-Command', '2>&1 | Out-File -Encoding default', '',
+          \               '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
           \ ['sh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
           \ ['ksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
           \ ['mksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
@@ -58,6 +60,9 @@
     if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
       let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'"
       let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'"
+    elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
+      let str1 = "'cmd \"arg1\" ''arg2'' !%#'"
+      let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\#'"
     else
       let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'"
       let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'"
@@ -135,6 +140,28 @@
   let &shell = save_shell
 endfunc
 
+" Test for 'shellslash'
+func Test_shellslash()
+  CheckOption shellslash
+  let save_shellslash = &shellslash
+  " The shell and cmdflag, and expected slash in tempname with shellslash set or
+  " unset.  The assert checks the file separator before the leafname.
+  " ".*\\\\[^\\\\]*$"
+  let shells = [['cmd', '/c', '\\', '/'],
+        \ ['powershell', '-Command', '\\', '/'],
+        \ ['sh', '-c', '/', '/']]
+  for e in shells
+    exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1]
+    set noshellslash
+    let file = tempname()
+    call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl')
+    set shellslash
+    let file = tempname()
+    call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl')
+  endfor
+  let &shellslash = save_shellslash
+endfunc
+
 " Test for 'shellxquote'
 func Test_shellxquote()
   CheckUnix
diff --git a/src/version.c b/src/version.c
index 9ec6877..dd74d2d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3071,
+/**/
     3070,
 /**/
     3069,