@@ -23,6 +23,7 @@ namespace Microsoft.PowerShell.EditorServices
23
23
using System . Management . Automation . Runspaces ;
24
24
using Microsoft . PowerShell . EditorServices . Session . Capabilities ;
25
25
using System . IO ;
26
+ using System . ComponentModel ;
26
27
27
28
/// <summary>
28
29
/// Manages the lifetime and usage of a PowerShell session.
@@ -768,7 +769,7 @@ await this.ExecuteCommand<object>(
768
769
/// <returns>A Task that can be awaited for completion.</returns>
769
770
public async Task ExecuteScriptWithArgs ( string script , string arguments = null , bool writeInputToHost = false )
770
771
{
771
- string launchedScript = script ;
772
+ var escapedScriptPath = new StringBuilder ( PowerShellContext . WildcardEscapePath ( script ) ) ;
772
773
PSCommand command = new PSCommand ( ) ;
773
774
774
775
if ( arguments != null )
@@ -796,21 +797,24 @@ public async Task ExecuteScriptWithArgs(string script, string arguments = null,
796
797
if ( File . Exists ( script ) || File . Exists ( scriptAbsPath ) )
797
798
{
798
799
// Dot-source the launched script path
799
- script = ". " + EscapePath ( script , escapeSpaces : true ) ;
800
+ string escapedFilePath = escapedScriptPath . ToString ( ) ;
801
+ escapedScriptPath = new StringBuilder ( ". " ) . Append ( QuoteEscapeString ( escapedFilePath ) ) ;
800
802
}
801
803
802
- launchedScript = script + " " + arguments ;
803
- command . AddScript ( launchedScript , false ) ;
804
+ // Add arguments
805
+ escapedScriptPath . Append ( ' ' ) . Append ( arguments ) ;
806
+
807
+ command . AddScript ( escapedScriptPath . ToString ( ) , false ) ;
804
808
}
805
809
else
806
810
{
807
- command . AddCommand ( script , false ) ;
811
+ command . AddCommand ( escapedScriptPath . ToString ( ) , false ) ;
808
812
}
809
813
810
814
if ( writeInputToHost )
811
815
{
812
816
this . WriteOutput (
813
- launchedScript + Environment . NewLine ,
817
+ script + Environment . NewLine ,
814
818
true ) ;
815
819
}
816
820
@@ -1113,30 +1117,145 @@ public async Task SetWorkingDirectory(string path, bool isPathAlreadyEscaped)
1113
1117
{
1114
1118
if ( ! isPathAlreadyEscaped )
1115
1119
{
1116
- path = EscapePath ( path , false ) ;
1120
+ path = WildcardEscapePath ( path ) ;
1117
1121
}
1118
1122
1119
1123
runspaceHandle . Runspace . SessionStateProxy . Path . SetLocation ( path ) ;
1120
1124
}
1121
1125
}
1122
1126
1127
+ /// <summary>
1128
+ /// Fully escape a given path for use in PowerShell script.
1129
+ /// Note: this will not work with PowerShell.AddParameter()
1130
+ /// </summary>
1131
+ /// <param name="path">The path to escape.</param>
1132
+ /// <returns>An escaped version of the path that can be embedded in PowerShell script.</returns>
1133
+ internal static string FullyPowerShellEscapePath ( string path )
1134
+ {
1135
+ string wildcardEscapedPath = WildcardEscapePath ( path ) ;
1136
+ return QuoteEscapeString ( wildcardEscapedPath ) ;
1137
+ }
1138
+
1139
+ /// <summary>
1140
+ /// Wrap a string in quotes to make it safe to use in scripts.
1141
+ /// </summary>
1142
+ /// <param name="escapedPath">The glob-escaped path to wrap in quotes.</param>
1143
+ /// <returns>The given path wrapped in quotes appropriately.</returns>
1144
+ internal static string QuoteEscapeString ( string escapedPath )
1145
+ {
1146
+ var sb = new StringBuilder ( escapedPath . Length + 2 ) ; // Length of string plus two quotes
1147
+ sb . Append ( '\' ' ) ;
1148
+ if ( ! escapedPath . Contains ( '\' ' ) )
1149
+ {
1150
+ sb . Append ( escapedPath ) ;
1151
+ }
1152
+ else
1153
+ {
1154
+ foreach ( char c in escapedPath )
1155
+ {
1156
+ if ( c == '\' ' )
1157
+ {
1158
+ sb . Append ( "''" ) ;
1159
+ continue ;
1160
+ }
1161
+
1162
+ sb . Append ( c ) ;
1163
+ }
1164
+ }
1165
+ sb . Append ( '\' ' ) ;
1166
+ return sb . ToString ( ) ;
1167
+ }
1168
+
1169
+ /// <summary>
1170
+ /// Return the given path with all PowerShell globbing characters escaped,
1171
+ /// plus optionally the whitespace.
1172
+ /// </summary>
1173
+ /// <param name="path">The path to process.</param>
1174
+ /// <param name="escapeSpaces">Specify True to escape spaces in the path, otherwise False.</param>
1175
+ /// <returns>The path with [ and ] escaped.</returns>
1176
+ internal static string WildcardEscapePath ( string path , bool escapeSpaces = false )
1177
+ {
1178
+ var sb = new StringBuilder ( ) ;
1179
+ for ( int i = 0 ; i < path . Length ; i ++ )
1180
+ {
1181
+ char curr = path [ i ] ;
1182
+ switch ( curr )
1183
+ {
1184
+ // Escape '[', ']', '?' and '*' with '`'
1185
+ case '[' :
1186
+ case ']' :
1187
+ case '*' :
1188
+ case '?' :
1189
+ case '`' :
1190
+ sb . Append ( '`' ) . Append ( curr ) ;
1191
+ break ;
1192
+
1193
+ default :
1194
+ // Escape whitespace if required
1195
+ if ( escapeSpaces && char . IsWhiteSpace ( curr ) )
1196
+ {
1197
+ sb . Append ( '`' ) . Append ( curr ) ;
1198
+ break ;
1199
+ }
1200
+ sb . Append ( curr ) ;
1201
+ break ;
1202
+ }
1203
+ }
1204
+
1205
+ return sb . ToString ( ) ;
1206
+ }
1207
+
1123
1208
/// <summary>
1124
1209
/// Returns the passed in path with the [ and ] characters escaped. Escaping spaces is optional.
1125
1210
/// </summary>
1126
1211
/// <param name="path">The path to process.</param>
1127
1212
/// <param name="escapeSpaces">Specify True to escape spaces in the path, otherwise False.</param>
1128
1213
/// <returns>The path with [ and ] escaped.</returns>
1214
+ [ EditorBrowsable ( EditorBrowsableState . Never ) ]
1215
+ [ Obsolete ( "This API is not meant for public usage and should not be used." ) ]
1129
1216
public static string EscapePath ( string path , bool escapeSpaces )
1130
1217
{
1131
- string escapedPath = Regex . Replace ( path , @"(?<!`)\[" , "`[" ) ;
1132
- escapedPath = Regex . Replace ( escapedPath , @"(?<!`)\]" , "`]" ) ;
1218
+ return WildcardEscapePath ( path , escapeSpaces ) ;
1219
+ }
1133
1220
1134
- if ( escapeSpaces )
1221
+ internal static string UnescapeWildcardEscapedPath ( string wildcardEscapedPath )
1222
+ {
1223
+ // Prevent relying on my implementation if we can help it
1224
+ if ( ! wildcardEscapedPath . Contains ( '`' ) )
1135
1225
{
1136
- escapedPath = Regex . Replace ( escapedPath , @"(?<!`) " , "` " ) ;
1226
+ return wildcardEscapedPath ;
1137
1227
}
1138
1228
1139
- return escapedPath ;
1229
+ var sb = new StringBuilder ( wildcardEscapedPath . Length ) ;
1230
+ for ( int i = 0 ; i < wildcardEscapedPath . Length ; i ++ )
1231
+ {
1232
+ // If we see a backtick perform a lookahead
1233
+ char curr = wildcardEscapedPath [ i ] ;
1234
+ if ( curr == '`' && i + 1 < wildcardEscapedPath . Length )
1235
+ {
1236
+ // If the next char is an escapable one, don't add this backtick to the new string
1237
+ char next = wildcardEscapedPath [ i + 1 ] ;
1238
+ switch ( next )
1239
+ {
1240
+ case '[' :
1241
+ case ']' :
1242
+ case '?' :
1243
+ case '*' :
1244
+ continue ;
1245
+
1246
+ default :
1247
+ if ( char . IsWhiteSpace ( next ) )
1248
+ {
1249
+ continue ;
1250
+ }
1251
+ break ;
1252
+ }
1253
+ }
1254
+
1255
+ sb . Append ( curr ) ;
1256
+ }
1257
+
1258
+ return sb . ToString ( ) ;
1140
1259
}
1141
1260
1142
1261
/// <summary>
@@ -1145,14 +1264,11 @@ public static string EscapePath(string path, bool escapeSpaces)
1145
1264
/// </summary>
1146
1265
/// <param name="path">The path to unescape.</param>
1147
1266
/// <returns>The path with the ` character before [, ] and spaces removed.</returns>
1267
+ [ EditorBrowsable ( EditorBrowsableState . Never ) ]
1268
+ [ Obsolete ( "This API is not meant for public usage and should not be used." ) ]
1148
1269
public static string UnescapePath ( string path )
1149
1270
{
1150
- if ( ! path . Contains ( "`" ) )
1151
- {
1152
- return path ;
1153
- }
1154
-
1155
- return Regex . Replace ( path , @"`(?=[ \[\]])" , "" ) ;
1271
+ return UnescapeWildcardEscapedPath ( path ) ;
1156
1272
}
1157
1273
1158
1274
#endregion
0 commit comments