Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for batcodecheck.cs

(view source code of batcodecheck.cs as plain text)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6.  
  7.  
  8. namespace RobvanderWoude
  9. {
  10. 	public partial class BatCodeCheck
  11. 	{
  12. 		public static string progver = "0.32.1 beta";
  13.  
  14. 		#region Global variables
  15.  
  16. 		public static int lasterrline = -1;
  17. 		public static int lastline = -1;
  18. 		public static int linenum = 0;
  19. 		public static int lineswitherrors = 0;
  20. 		public static int maxvarlen = 32; // Some day, we may have to adjust the accepted variable name length if 32 characters is not enough ([\w\.-]{1,32})
  21. 		public static int obsoletecmd = 0;
  22. 		public static int parenthesis = 0;
  23. 		public static int rc = 1;
  24. 		public static int setlocal = 0;
  25. 		public static int unterminatedexclamations = 0;
  26. 		public static int unterminateddoublequotes = 0;
  27. 		public static int unterminatedpercentsigns = 0;
  28. 		public static int unterminatedsinglequotes = 0;
  29. 		public static bool addtimestamp = false;
  30. 		public static bool delayedvarexp = false;
  31. 		public static bool delayedvarglobal = false;
  32. 		public static bool dispnums = true;
  33. 		public static bool errinline = false;
  34. 		public static bool htmlopen = false;
  35. 		public static bool includelocalvars = false;
  36. 		public static bool incomment = false;
  37. 		public static bool interactive = false;
  38. 		public static bool licensed = true;
  39. 		public static bool log2html = false;
  40. 		public static bool logging = false;
  41. 		public static bool replacehtml = true;
  42. 		public static bool replacelog = true;
  43. 		public static bool splitoneliners = false;
  44. 		public static bool wrap = false;
  45. 		public static string allargs;
  46. 		public static string allsrc;
  47. 		public static string allsrc_nocomments;
  48. 		public static string allerrors = String.Empty;
  49. 		public static string allbadpr = String.Empty;
  50. 		public static string alltips = String.Empty;
  51. 		public static string allerrsum = String.Empty;
  52. 		public static string dynvarpattern = @"__CD__|CD|CMDCMDLINE|CMDEXTVESION|DATE|ERRORLEVEL|HIGHESTNUMANODENUMBER|RANDOM|TIME";
  53. 		public static string html = String.Empty;
  54. 		public static string htmlfile = String.Empty;
  55. 		public static string intcmdpattern = @"APPEND|ASSOC|BREAK|CALL|CD|CHCP|CHDIR|CLS|COLOR|COPY|DATE|DEL|DIR|DPATH|ECHO|ENDLOCAL|ERASE|EXIT|FOR|FTYPE|GOTO|IF|KEYS|MD|MKDIR|MKLINK|MOVE|PATH|PAUSE|POPD|PROMPT|PUSHD|RD|REM|REN|RENAME|RMDIR|SET|SETLOCAL|SHIFT|START|TIME|TITLE|TRUENAME|TYPE|VER|VERIFY|VOL";
  56. 		public static string logfile = String.Empty;
  57. 		public static string notx32pattern = @"BREAK|CTTY|FASTOPEN|FASTOPEN\.COM|FASTOPEN\.EXE|KEYS|SHARE|SHARE\.EXE";
  58. 		public static string notx64pattern = @"APPEND|APPEND\.COM|APPEND\.EXE|DEBUG|DEBUG\.COM|DEBUG\.EXE";
  59. 		public static string source = String.Empty;
  60. 		public static string sysvarpattern = @"AllUsersProfile|AppData|CommonProgramFiles|CommonProgramFiles\(x86\)|CommonProgramW6432|ComputerName|ComSpec|HomeDrive|HomePath|LocalAppData|LogonServer|NUMBER_OF_PROCESSORS|OS|Path|PathExt|PROCESSOR_ARCHITECTURE|PROCESSOR_IDENTIFIER|PROCESSOR_LEVEL|PROCESSOR_REVISION|ProgramData|ProgramFiles|ProgramFiles\(x86\)|ProgramW6432|PROMPT|SystemDrive|SystemRoot|TEMP|TMP|UserDomain|UserName|UserProfile|windir";
  61. 		public static string timestamp = String.Empty;
  62. 		public static string[] path = ( Environment.CurrentDirectory + ";" + Environment.GetEnvironmentVariable( "PATH" ) ).Split( ';' );
  63. 		public static string[] pathext = ( ";" + Environment.GetEnvironmentVariable( "PATHEXT" ).ToLower( ) ).Split( ';' );
  64. 		public static List<string> labels = new List<string>( );
  65. 		public static List<string> upcaselabels = new List<string>( );
  66. 		public static List<string> gotoerrors = new List<string>( );
  67. 		public static List<string> callerrors = new List<string>( );
  68. 		public static List<string> envvars = new List<string>( );
  69. 		public static List<string> upcaseenvvars = new List<string>( );
  70. 		public static List<string> envvarerrors = new List<string>( );
  71. 		public static List<string> localenvvars = new List<string>( );
  72. 		public static RegexOptions ignorecase = RegexOptions.IgnoreCase;
  73. 		public static StreamWriter htmlstream;
  74. 		public static ConsoleColor badprbgcolor = ConsoleColor.Yellow;
  75. 		public static ConsoleColor badprfgcolor = ConsoleColor.Red;
  76. 		public static ConsoleColor commentfgscolor = ConsoleColor.DarkGreen;
  77. 		public static ConsoleColor doublequotedfgcolor = ConsoleColor.Yellow;
  78. 		public static ConsoleColor echoedtextfgcolor = ConsoleColor.Cyan;
  79. 		public static ConsoleColor linenumbgcolor = ConsoleColor.Gray;
  80. 		public static ConsoleColor linenumfgcolor = ConsoleColor.DarkMagenta;
  81. 		public static ConsoleColor parenthesisfgcolor = ConsoleColor.Red;
  82. 		public static ConsoleColor redirectionfgcolor = ConsoleColor.Red;
  83. 		public static ConsoleColor reportbgcolor = Console.BackgroundColor;
  84. 		public static ConsoleColor reportfgcolor = ConsoleColor.Red;
  85. 		public static ConsoleColor singlequotedfgcolor = ConsoleColor.DarkGray;
  86. 		public static ConsoleColor tipsbgcolor = ConsoleColor.Blue;
  87. 		public static ConsoleColor tipsfgcolor = ConsoleColor.White;
  88. 		public static ConsoleColor variablesfgcolor = ConsoleColor.Green;
  89. 		public static ConsoleColor warningbgcolor = ConsoleColor.Red;
  90. 		public static ConsoleColor warningfgcolor = ConsoleColor.White;
  91. 		public static ConsoleColor windowbgcolor = Console.BackgroundColor;
  92. 		public static ConsoleColor windowfgcolor = Console.ForegroundColor;
  93. 		// Inline variables
  94. 		public static string badprmsg = String.Empty;
  95. 		public static string errmsg = String.Empty;
  96. 		public static string tipsmsg = String.Empty;
  97. 		public static string message = String.Empty;
  98. 		public static string part = String.Empty;
  99. 		public static string buildword = String.Empty;
  100. 		public static string lastword = String.Empty;
  101.  
  102.  
  103. 		#endregion Global variables
  104.  
  105.  
  106. 		[STAThread]
  107. 		static int Main( string[] args )
  108. 		{
  109. 			#region Command Line Parsing
  110.  
  111. 			allargs = String.Join( " ", args );
  112.  
  113. 			#region Command Line Arguments
  114.  
  115. 			if ( args.Length == 0 || args.Length > 8 )
  116. 			{
  117. 				return WriteError( );
  118. 			}
  119. 			foreach ( string arg in args )
  120. 			{
  121. 				if ( arg[0] == '/' )
  122. 				{
  123. 					switch ( arg.ToUpper( ).Substring( 0, Math.Min( 2, arg.Length ) ) )
  124. 					{
  125. 						case "/?":
  126. 							return WriteError( );
  127. 						case "/E":
  128. 							if ( includelocalvars )
  129. 							{
  130. 								return WriteError( "Duplicate command line argument /E" );
  131. 							}
  132. 							includelocalvars = true;
  133. 							break;
  134. 						case "/H":
  135. 							if ( log2html )
  136. 							{
  137. 								return WriteError( "Duplicate command line argument /H" );
  138. 							}
  139. 							log2html = true;
  140. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  141. 							{
  142. 								htmlfile = arg.Substring( 3 );
  143. 							}
  144. 							break;
  145. 						case "/I":
  146. 							if ( interactive )
  147. 							{
  148. 								return WriteError( "Duplicate command line argument /I" );
  149. 							}
  150. 							interactive = true;
  151. 							break;
  152. 						case "/L":
  153. 							if ( logging )
  154. 							{
  155. 								return WriteError( "Duplicate command line argument /L" );
  156. 							}
  157. 							logging = true;
  158. 							if ( arg.Length > 3 && arg.ToUpper( ).Substring( 2, 1 ) == ":" )
  159. 							{
  160. 								logfile = arg.Substring( 3 );
  161. 							}
  162. 							break;
  163. 						case "/S":
  164. 							if ( splitoneliners )
  165. 							{
  166. 								return WriteError( "Duplicate command line argument /S" );
  167. 							}
  168. 							splitoneliners = true;
  169. 							break;
  170. 						case "/T":
  171. 							if ( addtimestamp )
  172. 							{
  173. 								return WriteError( "Duplicate command line argument /T" );
  174. 							}
  175. 							addtimestamp = true;
  176. 							break;
  177. 						case "/W":
  178. 							if ( wrap )
  179. 							{
  180. 								return WriteError( "Duplicate command line argument /W" );
  181. 							}
  182. 							wrap = true;
  183. 							break;
  184. 						default:
  185. 							return WriteError( String.Format( "Invalid command line argument \"{0}\"", arg ) );
  186. 					}
  187. 				}
  188. 				else
  189. 				{
  190. 					if ( String.IsNullOrEmpty( source ) )
  191. 					{
  192. 						source = arg;
  193. 					}
  194. 					else
  195. 					{
  196. 						return WriteError( String.Format( "Invalid command line argument \"{0}\"", arg ) );
  197. 					}
  198. 				}
  199. 			}
  200.  
  201. 			#endregion Command Line Arguments
  202.  
  203. 			// Validate paths if specified
  204. 			#region Validate paths
  205.  
  206. 			if ( !interactive )
  207. 			{
  208. 				if ( String.IsNullOrEmpty( source ) )
  209. 				{
  210. 					return WriteError( "Please specify a source file" );
  211. 				}
  212. 				if ( !addtimestamp && source.IndexOfAny( "/?*".ToCharArray( ) ) > -1 )
  213. 				{
  214. 					return WriteError( );
  215. 				}
  216. 				if ( !File.Exists( source ) )
  217. 				{
  218. 					return WriteError( "Source file not found" );
  219. 				}
  220. 			}
  221. 			if ( !String.IsNullOrWhiteSpace( source ) )
  222. 			{
  223. 				source = Path.GetFullPath( source );
  224. 			}
  225. 			if ( !String.IsNullOrEmpty( logfile ) )
  226. 			{
  227. 				if ( !Directory.Exists( Directory.GetParent( logfile ).FullName ) )
  228. 				{
  229. 					return WriteError( "Invalid path to log file" );
  230. 				}
  231. 				logfile = Path.GetFullPath( logfile );
  232. 			}
  233. 			if ( !String.IsNullOrEmpty( htmlfile ) )
  234. 			{
  235. 				if ( !Directory.Exists( Directory.GetParent( htmlfile ).FullName ) )
  236. 				{
  237. 					return WriteError( "Invalid path to HTML file" );
  238. 				}
  239. 				htmlfile = Path.GetFullPath( htmlfile );
  240. 			}
  241.  
  242. 			#endregion Validate paths
  243.  
  244. 			// Local environment variable %BatCodeCheck% can be used to set defaults, but it is overruled by command line arguments
  245. 			#region BatCodeCheck Environment Variable
  246.  
  247. 			try
  248. 			{
  249. 				string envargs = Environment.GetEnvironmentVariable( "BatCodeCheck" ).ToUpper( );
  250. 				if ( new Regex( @"^\s*(/[EHILSTW]\s*)+$" ).IsMatch( envargs ) )
  251. 				{
  252. 					foreach ( Match match in new Regex( "/[EHILSTW]" ).Matches( envargs ) )
  253. 					{
  254. 						switch ( match.ToString( ) )
  255. 						{
  256. 							case "/E":
  257. 								if ( !includelocalvars )
  258. 								{
  259. 									includelocalvars = true;
  260. 									allargs += " /E";
  261. 								}
  262. 								break;
  263. 							case "/H":
  264. 								if ( !log2html )
  265. 								{
  266. 									log2html = true;
  267. 									allargs += " /H";
  268. 								}
  269. 								break;
  270. 							case "/I":
  271. 								if ( !interactive )
  272. 								{
  273. 									interactive = true;
  274. 									allargs += " /I";
  275. 								}
  276. 								break;
  277. 							case "/L":
  278. 								if ( !logging )
  279. 								{
  280. 									logging = true;
  281. 									allargs += " /L";
  282. 								}
  283. 								break;
  284. 							case "/S":
  285. 								if ( !splitoneliners )
  286. 								{
  287. 									splitoneliners = true;
  288. 									allargs += " /S";
  289. 								}
  290. 								break;
  291. 							case "/T":
  292. 								if ( !addtimestamp )
  293. 								{
  294. 									addtimestamp = true;
  295. 									allargs += " /T";
  296. 								}
  297. 								break;
  298. 							case "/W":
  299. 								if ( !wrap )
  300. 								{
  301. 									wrap = true;
  302. 									allargs += " /W";
  303. 								}
  304. 								break;
  305. 						}
  306. 					}
  307. 				}
  308. 			}
  309. 			catch ( Exception )
  310. 			{
  311. 				// Environment variable BatCodeCheck not found
  312. 			}
  313.  
  314. 			#endregion BatCodeCheck Environment Variable
  315.  
  316. 			// Defaults
  317. 			#region Command Line Defaults
  318.  
  319. 			if ( !includelocalvars )
  320. 			{
  321. 				pathext = ".com;.exe;.bat;.cmd".Split( ';' );
  322. 			}
  323. 			if ( addtimestamp )
  324. 			{
  325. 				AddTimeStamp( );
  326. 			}
  327. 			if ( log2html && String.IsNullOrWhiteSpace( htmlfile ) )
  328. 			{
  329. 				htmlfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".html" );
  330. 			}
  331. 			if ( logging && String.IsNullOrWhiteSpace( logfile ) )
  332. 			{
  333. 				logfile = Path.Combine( Path.GetDirectoryName( source ), Path.GetFileNameWithoutExtension( source ) + timestamp + ".log" );
  334. 			}
  335.  
  336. 			#endregion Command Line Defaults
  337.  
  338. 			// Interactive (licensed version only)
  339. 			#region Interactive
  340.  
  341. 			if ( licensed && interactive )
  342. 			{
  343. 				try
  344. 				{
  345. 					if ( !ShowSettings( ) )
  346. 					{
  347. 						Console.Error.WriteLine( "Aborted by user request" );
  348. 						return 1;
  349. 					}
  350. 				}
  351. 				catch ( Exception )
  352. 				{
  353. 					return WriteError( "Interactive use of BatCodeCheck (/I switch) requires a licensed version" );
  354. 				}
  355. 			}
  356.  
  357. 			#endregion Interactive
  358.  
  359. 			#endregion Command Line Parsing
  360.  
  361. 			try
  362. 			{
  363. 				if ( File.Exists( source ) )
  364. 				{
  365. 					ReadSource( source );
  366. 					CollectEnvironmentVariables( );
  367. 					CollectLabels( );
  368. 					OpenHTML( );
  369.  
  370. 					// Now parse the source line by line
  371. 					rc = 0;
  372. 					foreach ( string line in allsrc.Split( new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None ) )
  373. 					{
  374. 						linenum += 1;
  375.  
  376. 						// If /S switch was used, split the line on parenthesis and ampersands, except parentheses surrounding the list in FOR loops, and except comments
  377. 						if ( splitoneliners && !new Regex( @"^\s*(::|REM)(\b|\s|$)", ignorecase ).IsMatch( line ) && line.IndexOfAny( "(&|)".ToCharArray( ) ) > -1 )
  378. 						{
  379. 							bool inelse = false;
  380. 							bool inforloop = false;
  381. 							string tmpline = String.Empty;
  382.  
  383. 							string pattern = @"(?<!\^)\((?!['\n\r])";
  384. 							Regex regex = new Regex( pattern );
  385. 							if ( regex.IsMatch( line ) )
  386. 							{
  387. 								MatchCollection matches = regex.Matches( line );
  388. 								tmpline = line.Substring( 0, matches[0].Index );
  389. 								if ( new Regex( @"\s%%[A-Z!]\s+IN\s+$", ignorecase ).IsMatch( tmpline ) )
  390. 								{
  391. 									inforloop = true;
  392. 								}
  393. 								else if ( new Regex( @"\)\s+ELSE\s+\(", ignorecase ).IsMatch( tmpline ) )
  394. 								{
  395. 									inelse = true;
  396. 								}
  397.  
  398. 							}
  399. 							if ( inforloop || inelse )
  400. 							{
  401. 								tmpline = regex.Replace( line, "(\n\t", 100, line.IndexOf( ')' ) + 1 );
  402. 							}
  403. 							else
  404. 							{
  405. 								tmpline = regex.Replace( line, "(\n\t" );
  406. 							}
  407.  
  408. 							pattern = @"(?<!['\^\n\r])\)";
  409. 							regex = new Regex( pattern );
  410. 							if ( inforloop || inelse )
  411. 							{
  412. 								tmpline = regex.Replace( tmpline, "\n)", 100, line.IndexOf( ')' ) + 1 );
  413. 							}
  414. 							else
  415. 							{
  416. 								tmpline = regex.Replace( tmpline, "\n)" );
  417. 							}
  418.  
  419. 							pattern = @"(?<!\^|2>|1>)&(?>!1|2)";
  420. 							regex = new Regex( pattern );
  421. 							tmpline = regex.Replace( tmpline, "\n\t" );
  422.  
  423. 							foreach ( string part in tmpline.Split( new string[] { "\n" }, StringSplitOptions.None ) )
  424. 							{
  425. 								if ( !String.IsNullOrWhiteSpace( part ) )
  426. 								{
  427. 									ParseLine( part );
  428. 								}
  429. 							}
  430. 						}
  431. 						else
  432. 						{
  433. 							ParseLine( line );
  434. 						}
  435. 					}
  436. 					DisplayMessagesSummary( );
  437. 					CloseHTML( );
  438. 					RestoreConsoleColors( );
  439. 					return rc;
  440. 				}
  441. 				else
  442. 				{
  443. 					return WriteError( String.Format( "File \"{0}\" not found", source ) );
  444. 				}
  445. 			}
  446. 			catch ( Exception e )
  447. 			{
  448. 				return WriteError( e.Message );
  449. 			}
  450. 		}
  451.  
  452.  
  453. 		#region Subroutines
  454.  
  455. 		public static void AddTimeStamp( )
  456. 		{
  457. 			if ( String.IsNullOrWhiteSpace( timestamp ) )
  458. 			{
  459. 				timestamp = DateTime.Now.ToString( ".yyyyMMddHHmmss" );
  460. 			}
  461. 		}
  462.  
  463. 		public static void CheckBadPractices( string line )
  464. 		{
  465. 			string pattern;
  466. 			Regex regex;
  467.  
  468. 			// SET dynamic variables
  469. 			#region SET dynamic variables
  470.  
  471. 			pattern = String.Format( @"\bSET\s+({0})=", dynvarpattern );
  472. 			regex = new Regex( pattern, ignorecase );
  473. 			if ( regex.IsMatch( line ) )
  474. 			{
  475. 				MatchCollection matches = regex.Matches( line );
  476. 				string dynvar = matches[0].Groups[1].Value;
  477. 				message = String.Format( "Setting dynamic variable \"{0}\" to a static value may cause problems later on in the batch file; consider using an alternative variable name instead", dynvar.ToUpper( ) );
  478. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  479. 				badprmsg += String.Format( "\n{0}.", message );
  480. 				errinline = true;
  481. 			}
  482.  
  483. 			#endregion SET dynamic variables
  484.  
  485. 			// SET system variables
  486. 			#region SET system variables
  487.  
  488. 			pattern = String.Format( @"\bSET\s+({0})=", sysvarpattern );
  489. 			regex = new Regex( pattern, ignorecase );
  490. 			if ( regex.IsMatch( line ) )
  491. 			{
  492. 				MatchCollection matches = regex.Matches( line );
  493. 				string dynvar = matches[0].Groups[1].Value;
  494. 				message = String.Format( "Setting system variable \"{0}\" may cause problems later on in the batch file; consider using an alternative variable name instead", dynvar.ToUpper( ) );
  495. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  496. 				badprmsg += String.Format( "\n{0}.", message );
  497. 				errinline = true;
  498. 			}
  499.  
  500. 			#endregion SET system variables
  501.  
  502. 			// Quoted paths
  503. 			// Disabled for now, as it is extremely hard, if possible at all, to extract a path from a command line without a true interpreter
  504. 			#region Quoted paths
  505. 			/*
  506. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\s\\]+\s+[^\\]+(?:\\[^\\]+)*(?:\\|\b|$))";
  507. 			regex = new Regex( pattern, ignorecase );
  508. 			if ( regex.IsMatch( line ) )
  509. 			{
  510. 				MatchCollection matches = regex.Matches( line );
  511. 				string path = matches[0].Groups[1].ToString( );
  512. 				message = String.Format( "Whitespace found in unquoted path: \"{0}\"", path );
  513. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  514. 				badprmsg += String.Format( "\n{0}.", message );
  515. 				errinline = true;
  516. 			}
  517.  
  518. 			pattern = @"[^""]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(\(|\))[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  519. 			regex = new Regex( pattern, ignorecase );
  520. 			if ( regex.IsMatch( line ) )
  521. 			{
  522. 				MatchCollection matches = regex.Matches( line );
  523. 				string path = matches[0].Groups[1].ToString( );
  524. 				string spec = matches[0].Groups[2].ToString( );
  525. 				message = String.Format( "Parenthesis found in unquoted path: \"{1}\"; use doublequotes, especially inside code blocks", spec, path );
  526. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  527. 				badprmsg += String.Format( "\n{0}.", message );
  528. 				errinline = true;
  529. 			}
  530.  
  531. 			pattern = @"[^""=]((?:[A-Z]:|\\\\\w+\\\w+)(?:\\[^\\]+)*\\[^\\]*(&|'|,|\^)[^\\]*(?:\\[^\\]+)*\\?(?:\b|$))";
  532. 			regex = new Regex( pattern, ignorecase );
  533. 			if ( regex.IsMatch( line ) )
  534. 			{
  535. 				MatchCollection matches = regex.Matches( line );
  536. 				string path = matches[0].Groups[1].ToString( );
  537. 				string spec = matches[0].Groups[2].ToString( );
  538. 				message = String.Format( "Special character \"{0}\" found in unquoted path: \"{1}\"; use doublequotes", spec, path );
  539. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  540. 				badprmsg += String.Format( "\n{0}.", message );
  541. 				errinline = true;
  542. 			}
  543. 			*/
  544. 			#endregion Quoted paths
  545. 		}
  546.  
  547. 		public static void CheckBestPractices( string line )
  548. 		{
  549. 			string pattern;
  550. 			Regex regex;
  551.  
  552. 			// Doublequotes in SET statements
  553. 			#region Doublequoted SET
  554.  
  555. 			pattern = @"(?:^|\b|\()(SET\s+""([\w_\.-]+)=([^""&\n\r]+)"")";
  556. 			regex = new Regex( pattern, ignorecase );
  557. 			if ( regex.IsMatch( line ) )
  558. 			{
  559. 				foreach ( Match match in regex.Matches( line ) )
  560. 				{
  561. 					string wrong = match.Groups[1].ToString( );
  562. 					string correct = String.Format( "SET {0}={1}", match.Groups[2].ToString( ), match.Groups[3].ToString( ) );
  563. 					message = String.Format( "Doublequoted SET statements are not recommended, except with /A (or /P) switch; use {0} instead of {1}", correct, wrong );
  564. 					alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  565. 					tipsmsg += String.Format( "\n{0}.", message );
  566. 					errinline = true;
  567. 				}
  568. 			}
  569.  
  570. 			#endregion Doublequoted SET
  571.  
  572. 			// Whitespace around equal signs in IF statements
  573. 			#region Whitespace around equal signs
  574.  
  575. 			pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*==?\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  576. 			regex = new Regex( pattern, ignorecase );
  577. 			if ( regex.IsMatch( line ) )
  578. 			{
  579. 				pattern = @"(?:^|\b)(IF\s+(/I\s+)?(\w+|\""[^""]+\"")(?:\s+==?\s*|\s*==?\s+)(\w+|\""[^""]+\""))\s*[\(\w]";
  580. 				regex = new Regex( pattern, ignorecase );
  581. 				if ( regex.IsMatch( line ) )
  582. 				{
  583. 					MatchCollection matches = regex.Matches( line );
  584. 					string wrong = matches[0].Groups[1].ToString( );
  585. 					string correct = String.Format( "IF {0}{1}=={2}", ( String.IsNullOrWhiteSpace( matches[0].Groups[2].ToString( ) ) ? String.Empty : "/I " ), matches[0].Groups[3].ToString( ), matches[0].Groups[4].ToString( ) );
  586. 					message = String.Format( "Whitespace around the double equal sign in an IF statement is not recommended, use {0} instead of {1}", correct, wrong );
  587. 					alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  588. 					tipsmsg += String.Format( "\n{0}.", message );
  589. 					errinline = true;
  590. 				}
  591. 			}
  592.  
  593. 			#endregion Whitespace around equal signs
  594.  
  595. 			// Devices with appended colons
  596. 			#region Devices with appended colons
  597.  
  598. 			pattern = @"(?:>|\b)(AUX|CLOCK\$|COM[1-8]|CON|LPT[1-8]|NUL|PRN):(?:\b|$)";
  599. 			regex = new Regex( pattern, ignorecase );
  600. 			if ( regex.IsMatch( line ) )
  601. 			{
  602. 				MatchCollection matches = regex.Matches( line );
  603. 				string device = matches[0].Groups[1].ToString( ).ToUpper( );
  604. 				message = String.Format( "Redundant colon appended to device name \"{0}\"", device );
  605. 				alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  606. 				tipsmsg += String.Format( "\n{0}.", message );
  607. 				errinline = true;
  608. 			}
  609.  
  610. 			#endregion Devices with appended colons
  611.  
  612. 			// Obsolete commands BREAK, CTTY, KEYS, and 16-bit commands
  613. 			#region Obsolete commands
  614.  
  615. 			pattern = String.Format( @"(?:^|&|\|\()\s*({0})\b", notx32pattern );
  616. 			regex = new Regex( pattern, ignorecase );
  617. 			if ( regex.IsMatch( line ) )
  618. 			{
  619. 				MatchCollection matches = regex.Matches( line );
  620. 				foreach ( Match match in matches )
  621. 				{
  622. 					string cmd = match.Groups[1].ToString( ).ToUpper( );
  623. 					message = String.Format( "Obsolete command \"{0}\" is accepted only for MS-DOS backwards compatibility", cmd );
  624. 					alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  625. 					tipsmsg += String.Format( "\n{0}.", message );
  626. 					obsoletecmd += 1;
  627. 				}
  628. 				errinline = true;
  629. 			}
  630.  
  631. 			pattern = String.Format( @"(?:^|&|\|\()\s*({0})\b", notx64pattern );
  632. 			regex = new Regex( pattern, ignorecase );
  633. 			if ( regex.IsMatch( line ) )
  634. 			{
  635. 				MatchCollection matches = regex.Matches( line );
  636. 				foreach ( Match match in matches )
  637. 				{
  638. 					string cmd = match.Groups[1].ToString( ).ToUpper( );
  639. 					if ( System.Environment.Is64BitOperatingSystem )
  640. 					{
  641. 						message = String.Format( "16-bit command \"{0}\" is not available in 64-bit Windows", cmd );
  642. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  643. 						errmsg += String.Format( "\n{0}.", message );
  644. 					}
  645. 					else
  646. 					{
  647. 						message = String.Format( "16-bit command \"{0}\" will not be available in 64-bit Windows", cmd );
  648. 						alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  649. 						tipsmsg += String.Format( "\n{0}.", message );
  650. 					}
  651. 					obsoletecmd += 1;
  652. 				}
  653. 				errinline = true;
  654. 			}
  655.  
  656. 			#endregion Obsolete commands
  657.  
  658. 			// @ all over the place
  659. 			#region Hiding command echo
  660.  
  661. 			if ( linenum > 1 )
  662. 			{
  663. 				pattern = @"^\s*@(?>!ECHO\s*(ON|OFF)\s)";
  664. 				regex = new Regex( pattern, ignorecase );
  665. 				if ( regex.IsMatch( line ) )
  666. 				{
  667. 					message = "Using multiple @s to hide command echoing is NOT recommended, as it makes debugging batch files harder. @ECHO OFF at the start of the batch file suffices to hide ALL command echoing.";
  668. 					alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  669. 					tipsmsg += String.Format( "\n{0}.", message );
  670. 					errinline = true;
  671. 				}
  672. 			}
  673. 			#endregion Hiding command echo
  674. 		}
  675.  
  676. 		public static void CheckCommonMistakes( string line )
  677. 		{
  678. 			string pattern;
  679. 			Regex regex;
  680.  
  681. 			// IF [NOT] EXISTS or IF [NOT] EXIT or IF [NOT] EXITS instead of IF EXIST
  682. 			#region IF EXIST
  683.  
  684. 			pattern = @"\bIF(\s+NOT)?\s+EXI(STS|T|TS)\s";
  685. 			regex = new Regex( pattern, ignorecase );
  686. 			if ( regex.IsMatch( line ) )
  687. 			{
  688. 				//message = "Found \"IF EXISTS ...\" (note the incorrect trailing \"S\"); use \"IF EXIST ...\" (without trailing \"S\") instead";
  689. 				MatchCollection matches = regex.Matches( line );
  690. 				string matchall = matches[0].ToString( ).ToUpper( );
  691. 				string matchnot = matches[0].Groups[1].ToString( ).ToUpper( );
  692. 				string matchend = matches[0].Groups[2].ToString( ).ToUpper( );
  693. 				switch ( matchend )
  694. 				{
  695. 					case "STS":
  696. 						message = String.Format( "Found \"{0} ...\" (note the incorrect trailing \"S\"); use \"IF{1} EXIST ...\" (without trailing \"S\") instead", matchall, matchnot );
  697. 						break;
  698. 					case "T":
  699. 						message = String.Format( "Found \"{0} ...\" (note the missing \"S\"); use \"IF{1} EXIST ...\" (with \"S\") instead", matchall, matchnot );
  700. 						break;
  701. 					case "TS":
  702. 						message = String.Format( "Found \"{0} ...\" (note the swapped \"S\" and \"T\"); use \"IF{1} EXIST ...\" (\"S\" first, followed by \"T\") instead", matchall, matchnot );
  703. 						break;
  704. 				}
  705. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  706. 				errmsg += String.Format( "\n{0}.", message );
  707. 				errinline = true;
  708. 			}
  709.  
  710. 			#endregion IF EXIST
  711.  
  712. 			// Common typos for %AllUsersProfile% variable
  713. 			#region AllUsersProfile
  714.  
  715. 			pattern = @"(%|!)(A+L+USERPROFILES?)(\1)";
  716. 			regex = new Regex( pattern, ignorecase );
  717. 			if ( regex.IsMatch( line ) )
  718. 			{
  719. 				MatchCollection matches = regex.Matches( line );
  720. 				foreach ( Match match in matches )
  721. 				{
  722. 					string wrong = match.Groups[2].ToString( ).ToUpper( );
  723. 					string varchr = match.Groups[1].ToString( );
  724. 					message = String.Format( "Possible typo: the system variable name is spelled \"{0}ALLUSERSPROFILE{0}\", not \"{0}{1}{0}\"", varchr, wrong );
  725. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  726. 					errmsg += String.Format( "\n{0}.", message );
  727. 					errinline = true;
  728. 				}
  729. 			}
  730.  
  731. 			#endregion AllUsersProfile
  732.  
  733. 			// IF [NOT] ERRORLEVEL 0
  734. 			#region IF ERRORLEVEL 0
  735.  
  736. 			pattern = @"(^|\b|\()IF\s+ERRORLEVEL\s+0\s";
  737. 			regex = new Regex( pattern, ignorecase );
  738. 			if ( regex.IsMatch( line ) )
  739. 			{
  740. 				message = "IF ERRORLEVEL 0 tests if the errorlevel is greater than or equal to 0, so it ALWAYS returns true; use either IF NOT ERRORLEVEL 1 or IF %ErrorLevel% EQU 0 instead";
  741. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  742. 				errmsg += String.Format( "\n{0}.", message );
  743. 				errinline = true;
  744. 				rc = linenum;
  745. 			}
  746.  
  747. 			pattern = @"(^|\b|\()IF\s+NOT\s+ERRORLEVEL\s+0\s";
  748. 			regex = new Regex( pattern, ignorecase );
  749. 			if ( regex.IsMatch( line ) )
  750. 			{
  751. 				message = "IF NOT ERRORLEVEL 0 tests if the errorlevel is less than 0, so it always ALWAYS returns false; use either IF ERRORLEVEL 1 or IF %ErrorLevel% NEQ 0 instead";
  752. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  753. 				errmsg += String.Format( "\n{0}.", message );
  754. 				errinline = true;
  755. 				rc = linenum;
  756. 			}
  757.  
  758. 			#endregion IF ERRORLEVEL 0
  759.  
  760. 			// :: inside code blocks
  761. 			#region Double colons in code blocks
  762.  
  763. 			// check if :: is used inside a code block, but skip REM or ECHO followed by ::
  764. 			bool codeblock = ( parenthesis > 0 );
  765. 			bool dblcolcmnt = ( line.IndexOf( "::" ) > line.IndexOfAny( "(&|".ToCharArray( ) ) );
  766. 			bool echoedcmnt = new Regex( @"^([^:]*|([^:]*:[^:]+)*)ECHO\b[^\n\r&\|\>]*::", ignorecase ).IsMatch( line ); // Skip if ECHO is found before :: and no redirection in between
  767. 			bool remcomment = new Regex( @"^([^:]*|([^:]*:[^:]+)*)REM\b[^\n\r&]*::", ignorecase ).IsMatch( line ); // Skip if REM is found before :: and no ampersand in between
  768. 			if ( dblcolcmnt && codeblock && !remcomment && !echoedcmnt )
  769. 			{
  770. 				message = "Found \"::\" inside a code block (i.e. in parenthesis); use \"REM\" instead";
  771. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  772. 				errmsg += String.Format( "\n{0}.", message );
  773. 				errinline = true;
  774. 				rc = linenum;
  775. 			}
  776.  
  777. 			#endregion Double colons in code blocks
  778.  
  779. 			// CALLs to undefined subroutines
  780. 			#region CALLs
  781.  
  782. 			if ( new Regex( @"(?:^|\b)CALL\s+:", ignorecase ).IsMatch( line ) )
  783. 			{
  784. 				MatchCollection matches = new Regex( @"(?:^|\b)CALL\s+:([\w\.\[\]/+\-\(\)\{\}]{1,127})", ignorecase ).Matches( line );
  785. 				foreach ( Match match in matches )
  786. 				{
  787. 					string label = match.Groups[1].ToString( );
  788. 					if ( !labels.Contains( label ) )
  789. 					{
  790. 						callerrors.Add( label );
  791. 						message = String.Format( "A CALL is made to a subroutine named \"{0}\" but the subroutine was not found; note that subroutine labels are case sensitive", label );
  792. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  793. 						errmsg += String.Format( "\n{0}.", message );
  794. 						errinline = true;
  795. 					}
  796. 				}
  797. 			}
  798.  
  799. 			#endregion CALLs
  800.  
  801. 			// GOTOs to undefined labels
  802. 			#region GOTOs
  803.  
  804. 			if ( new Regex( @"(?:^|\b)GOTO\s", ignorecase ).IsMatch( line ) )
  805. 			{
  806. 				MatchCollection matches = new Regex( @"(?:^|\b)GOTO\s+:?([\w\.\[\]/+\-]{1,127})[\)&|\s]*", ignorecase ).Matches( line );
  807. 				foreach ( Match match in matches )
  808. 				{
  809. 					string label = match.Groups[1].ToString( );
  810. 					if ( label.ToUpper( ) != "EOF" && !upcaselabels.Contains( label.ToUpper( ) ) )
  811. 					{
  812. 						gotoerrors.Add( label );
  813. 						message = String.Format( "A jump (GOTO) to a label named \"{0}\" was detected, but the label was not found", label );
  814. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  815. 						errmsg += String.Format( "\n{0}.", message );
  816. 						errinline = true;
  817. 					}
  818. 				}
  819. 			}
  820.  
  821. 			#endregion GOTOs
  822.  
  823. 			// Redirected Standard Error typos
  824. 			#region Redirected Standard Error
  825.  
  826. 			pattern = @"\s(2(?:>>|<+|\s+)&\s*1)(?:\b|$)";
  827. 			regex = new Regex( pattern, ignorecase );
  828. 			if ( regex.IsMatch( line ) )
  829. 			{
  830. 				MatchCollection matches = regex.Matches( line );
  831. 				string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  832. 				message = String.Format( "Incorrect redirection of Standard Error to Standard Output: \"{0}\"", redirect );
  833. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  834. 				errmsg += String.Format( "\n{0}.", message );
  835. 				errinline = true;
  836. 			}
  837.  
  838. 			pattern = @"\s(1(?:>>|<+|\s+)&\s*2)(?:\b|$)";
  839. 			regex = new Regex( pattern, ignorecase );
  840. 			if ( regex.IsMatch( line ) )
  841. 			{
  842. 				MatchCollection matches = regex.Matches( line );
  843. 				string redirect = matches[0].Groups[1].ToString( ).ToUpper( );
  844. 				message = String.Format( "Incorrect redirection of Standard Output to Standard Error: \"{0}\"", redirect );
  845. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  846. 				errmsg += String.Format( "\n{0}.", message );
  847. 				errinline = true;
  848. 			}
  849.  
  850. 			#endregion Redirected Standard Error
  851.  
  852. 			// Unescaped redirection in FOR /F loops
  853. 			#region Unescaped redirection in FOR /F loops
  854.  
  855. 			pattern = @"FOR\s+/F\s+(""((delims|eol|skip|tokens)=[^ ""]*\s*|usebackq\s*)*"")?\s*%%[A-Z?]\s+IN\s+\('[^\^']*[\&\<\>\|]";
  856. 			regex = new Regex( pattern, ignorecase );
  857. 			if ( regex.IsMatch( line ) )
  858. 			{
  859. 				message = "Unescaped redirection symbol(s) in a FOR /F loop";
  860. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  861. 				errmsg += String.Format( "\n{0}.", message );
  862. 				errinline = true;
  863. 			}
  864.  
  865. 			#endregion Unescaped redirection in FOR /F loops
  866.  
  867. 			// Unescaped ECHOed parenthesis inside code block
  868. 			#region Unescaped ECHOed parenthesis inside code block
  869.  
  870. 			if ( parenthesis > 0 ) // if we're inside a code block...
  871. 			{
  872. 				pattern = @"(^|\b)ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  873. 			}
  874. 			else
  875. 			{
  876. 				pattern = @"\(\s*ECHO(?=\.|\s)[^&\|\n\r\(\)^]*[^^][\(\)]";
  877. 			}
  878. 			regex = new Regex( pattern, ignorecase );
  879. 			if ( regex.IsMatch( line ) )
  880. 			{
  881. 				message = "Unescaped ECHOed parenthesis inside a code block";
  882. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  883. 				errmsg += String.Format( "\n{0}.", message );
  884. 				errinline = true;
  885. 			}
  886.  
  887. 			#endregion Unescaped ECHOed parenthesis inside code block
  888.  
  889. 			// Delayed variables with delayed variable expansion disabled vv.
  890. 			#region Delayed variables without delayed expansion
  891.  
  892. 			if ( delayedvarexp )
  893. 			{
  894. 				pattern = @"%\w{1,16}%";
  895. 				regex = new Regex( pattern );
  896. 				if ( regex.IsMatch( line ) )
  897. 				{
  898. 					string envvar = regex.Match( line ).Value.ToUpper( );
  899. 					string varname = envvar.Substring( 1, envvar.Length - 2 );
  900. 					// Skip warning if we are dealing with a (dynamic) system variable
  901. 					pattern = String.Format( @"^({0}|{1})$", dynvarpattern, sysvarpattern );
  902. 					if ( !new Regex( pattern, ignorecase ).IsMatch( varname ) )
  903. 					{
  904. 						message = String.Format( "Variable \"{0}\" found with delayed variable expansion enabled; shouldn't that be \"!{1}!\" instead?", envvar, varname );
  905. 						// Skip warning if line is a comment
  906. 						if ( !new Regex( @"(^|&)\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  907. 						{
  908. 							allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  909. 							errmsg += String.Format( "\n{0}.", message );
  910. 							errinline = true;
  911. 						}
  912. 					}
  913. 				}
  914. 			}
  915. 			else
  916. 			{
  917. 				pattern = @"!\w{1,16}!";
  918. 				regex = new Regex( pattern );
  919. 				if ( regex.IsMatch( line ) )
  920. 				{
  921. 					// Skip warning if line is a comment
  922. 					if ( !new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  923. 					{
  924. 						if ( delayedvarglobal )
  925. 						{
  926. 							message = String.Format( "\"{0}\" looks like a delayed variable, check if delayed variable expansion is enabled", regex.Match( line ).Value.ToUpper( ) );
  927. 							allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  928. 							badprmsg += String.Format( "\n{0}.", message );
  929. 						}
  930. 						else
  931. 						{
  932. 							message = String.Format( "\"{0}\" looks like a delayed variable, but delayed variable expansion is not enabled", regex.Match( line ).Value.ToUpper( ) );
  933. 							allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  934. 							errmsg += String.Format( "\n{0}.", message );
  935. 						}
  936. 						errinline = true;
  937. 					}
  938. 				}
  939. 			}
  940.  
  941. 			#endregion Delayed variables without delayed expansion
  942.  
  943. 			// Excess number of delimiters in FOR /F loop
  944. 			#region Too many delimiters
  945.  
  946. 			pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]{5,})(?:\s[^""]+)?\""";
  947. 			regex = new Regex( pattern, ignorecase );
  948. 			if ( regex.IsMatch( line ) )
  949. 			{
  950. 				string delims = regex.Match( line ).Groups[1].ToString( );
  951. 				List<char> nodups = new List<char>( );
  952. 				foreach ( char del in delims )
  953. 				{
  954. 					if ( !nodups.Contains( del ) )
  955. 					{
  956. 						nodups.Add( del );
  957. 					}
  958. 				}
  959. 				if ( nodups.Count == delims.Length )
  960. 				{
  961. 					message = String.Format( "Found {0} delimiters in a FOR /F loop: \"{1}\". Are you trying to split a string on a WORD? The value of \"delims\" is a CHARACTER array, each individual character is a delimiter", delims.Length, delims, delims.Length - nodups.Count );
  962. 					alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  963. 					tipsmsg += String.Format( "\n{0}.", message );
  964. 				}
  965. 				else
  966. 				{
  967. 					message = String.Format( "Found {0} delimiters in a FOR /F loop: \"{1}\" (including {2} duplicates). Are you trying to split a string on a WORD? The value of \"delims\" is a CHARACTER array, each individual character is a delimiter", delims.Length, delims, delims.Length - nodups.Count );
  968. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  969. 					errmsg += String.Format( "\n{0}.", message );
  970. 				}
  971. 				errinline = true;
  972. 			}
  973. 			else
  974. 			{
  975. 				pattern = @"(?<!\w)FOR\s+/F\s+\""(?:[^""]+\s)?delims=([^\s\)\""]+)(?:\s[^""]+)?\""";
  976. 				regex = new Regex( pattern, ignorecase );
  977. 				if ( regex.IsMatch( line ) )
  978. 				{
  979. 					string delims = regex.Match( line ).Groups[1].ToString( );
  980. 					List<char> nodups = new List<char>( );
  981. 					foreach ( char del in delims )
  982. 					{
  983. 						if ( !nodups.Contains( del ) )
  984. 						{
  985. 							nodups.Add( del );
  986. 						}
  987. 					}
  988. 					if ( delims.Length > nodups.Count )
  989. 					{
  990. 						message = String.Format( "Found {0} duplicate delimiters (delims={1}) in a FOR /F loop", delims.Length - nodups.Count, delims );
  991. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  992. 						errmsg += String.Format( "\n{0}.", message );
  993. 						errinline = true;
  994. 					}
  995. 				}
  996. 			}
  997.  
  998. 			#endregion Too many delimiters
  999.  
  1000. 			// Wildcards in IF comparison
  1001. 			#region Wildcards in IF comparison
  1002.  
  1003. 			pattern = @"\bIF\s+(?:/I\s+)?(?:NOT\s+)?\""%\w{1,16}%\""\s*==\s*\""[^""\*\?]*[\*\?]";
  1004. 			regex = new Regex( pattern, ignorecase );
  1005. 			if ( regex.IsMatch( line ) )
  1006. 			{
  1007. 				message = "Wildcard character(s) found in IF comparison; IF \"%var%\"==\"literal_string\" does not accept wildcards; though asterisks and exclamation marks CAN be valid literals, make sure you did not try to apply them as wildcards";
  1008. 				alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1009. 				tipsmsg += String.Format( "\n{0}.", message );
  1010. 				errinline = true;
  1011. 			}
  1012.  
  1013. 			#endregion Wildcards in IF comparison
  1014.  
  1015. 			// Undefined variables
  1016. 			#region Undefined variables
  1017.  
  1018. 			// Skip this test if variable is inside comment
  1019. 			pattern = @"^\s*(::|REM\s)";
  1020. 			regex = new Regex( pattern, ignorecase );
  1021. 			if ( !regex.IsMatch( line ) )
  1022. 			{
  1023. 				// This regex pattern catches "straight" variables as well as "nested" ones
  1024. 				pattern = @"((?<!%)%{1,2}|(?:\^!)?!)([\w\.-]{1," + maxvarlen + @"})(\1|:)";
  1025. 				regex = new Regex( pattern, ignorecase );
  1026. 				if ( regex.IsMatch( line ) )
  1027. 				{
  1028. 					MatchCollection matches = regex.Matches( line );
  1029. 					foreach ( Match match in matches )
  1030. 					{
  1031. 						string envvar = match.Groups[2].ToString( );
  1032. 						if ( !upcaseenvvars.Contains( envvar.ToUpper( ) ) )
  1033. 						{
  1034. 							if ( !envvarerrors.Contains( envvar.ToUpper( ) ) )
  1035. 							{
  1036. 								envvarerrors.Add( envvar.ToUpper( ) );
  1037. 							}
  1038. 							if ( includelocalvars || !localenvvars.Contains( envvar.ToUpper( ) ) )
  1039. 							{
  1040. 								message = String.Format( "Undefined variable \"{0}\"", envvar );
  1041. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1042. 								errmsg += String.Format( "\n{0}.", message );
  1043. 							}
  1044. 							else
  1045. 							{
  1046. 								message = String.Format( "Variable \"{0}\" is never set in the batch file; however, the variable IS set in this computer's local environment. Use BatCodeCheck's \"/L\" switch to include the local environment variables", envvar );
  1047. 								allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1048. 								badprmsg += String.Format( "\n{0}.", message );
  1049. 							}
  1050. 							errinline = true;
  1051. 						}
  1052. 					}
  1053. 				}
  1054. 			}
  1055.  
  1056. 			#endregion Undefined variables
  1057.  
  1058. 			// count SETLOCAL and ENDLOCAL pairs
  1059. 			#region SETLOCAL ENDLOCAL pairs
  1060.  
  1061. 			pattern = @"^\s*SETLOCAL\b";
  1062. 			regex = new Regex( pattern, ignorecase );
  1063. 			if ( regex.IsMatch( line ) )
  1064. 			{
  1065. 				setlocal += 1;
  1066. 			}
  1067. 			if ( setlocal > 1 )
  1068. 			{
  1069. 				message = "Found a nested SETLOCAL";
  1070. 				if ( allerrors.IndexOf( message ) > -1 )
  1071. 				{
  1072. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1073. 					errmsg += String.Format( "\n{0}.", message );
  1074. 				}
  1075. 				errinline = true;
  1076. 			}
  1077.  
  1078. 			pattern = @"^\s*ENDLOCAL\b";
  1079. 			regex = new Regex( pattern, ignorecase );
  1080. 			if ( regex.IsMatch( line ) )
  1081. 			{
  1082. 				setlocal -= 1;
  1083. 			}
  1084. 			if ( setlocal < 0 )
  1085. 			{
  1086. 				message = "Number of ENDLOCALs exceeds number of SETLOCALs";
  1087. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1088. 				errmsg += String.Format( "\n{0}.", message );
  1089. 				errinline = true;
  1090. 			}
  1091.  
  1092. 			#endregion SETLOCAL ENDLOCAL pairs
  1093.  
  1094. 			// Whitespace and equal signs in IF statements
  1095. 			#region Whitespace and equal signs in IF statements
  1096.  
  1097. 			pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*==?\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1098. 			regex = new Regex( pattern, ignorecase );
  1099. 			if ( regex.IsMatch( line ) )
  1100. 			{
  1101. 				// Single equal sign
  1102. 				pattern = @"(^|\b)IF\s+(/I\s+)?(?:\w+|\""[^""]+\"")\s*=\s*(?:\w+|\""[^""]+\"")\s*[\(\w]";
  1103. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  1104. 				if ( regex.IsMatch( line ) )
  1105. 				{
  1106. 					message = "Single equal sign in IF statement, where a double equal sign is required";
  1107. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1108. 					errmsg += String.Format( "\n{0}.", message );
  1109. 					errinline = true;
  1110. 				}
  1111. 				// Whitespace before code block
  1112. 				pattern = @"(?:^|\b)(IF\s+(/I\s+)?(\w+|\""[^""]+\"")\s*==?\s*(\w+|\""[^""]+\"")\()";
  1113. 				regex = new Regex( pattern, ignorecase );
  1114. 				if ( regex.IsMatch( line ) )
  1115. 				{
  1116. 					MatchCollection matches = regex.Matches( line );
  1117. 					string wrong = matches[0].ToString( );
  1118. 					string correct = String.Format( "IF {0}{1}=={2} (", ( String.IsNullOrWhiteSpace( matches[0].Groups[2].ToString( ) ) ? String.Empty : "/I " ), matches[0].Groups[3].ToString( ), matches[0].Groups[4].ToString( ) );
  1119. 					message = String.Format( "Insert whitespace after the IF comparison, before the opening parenthesis of the code block: {0}...) instead of {1}...)", correct, wrong );
  1120. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1121. 					errmsg += String.Format( "\n{0}.", message );
  1122. 					errinline = true;
  1123. 				}
  1124. 			}
  1125.  
  1126. 			#endregion Whitespace and equal signs in IF statements
  1127. 		}
  1128.  
  1129. 		public static void CheckCommandLineArguments( string line )
  1130. 		{
  1131. 			string pattern;
  1132. 			Regex regex;
  1133.  
  1134. 			// Invalid arguments for ASSOC command
  1135. 			#region ASSOC command line
  1136.  
  1137. 			pattern = @"(^|\b|\()ASSOC(\b|$)";
  1138. 			regex = new Regex( pattern, ignorecase );
  1139. 			if ( regex.IsMatch( line ) )
  1140. 			{
  1141. 				pattern = @"(?:^|\b|\()ASSOC(\s+\.\w+(?:=[\w \.\-]{3,})?)?\s*$";
  1142. 				regex = new Regex( pattern, ignorecase );
  1143. 				if ( !regex.IsMatch( line ) )
  1144. 				{
  1145. 					pattern = @"(?:^|\b|\()ASSOC([^\n\r&|]+)";
  1146. 					regex = new Regex( pattern, ignorecase );
  1147. 					if ( regex.IsMatch( line ) )
  1148. 					{
  1149. 						string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  1150. 						if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  1151. 						{
  1152. 							message = String.Format( "Invalid command line argument(s) \"{0}\" for ASSOC command", cla );
  1153. 							allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1154. 							errmsg += String.Format( "\n{0}.", message );
  1155. 							errinline = true;
  1156. 						}
  1157. 					}
  1158. 				}
  1159. 			}
  1160.  
  1161. 			#endregion ASSOC command line
  1162.  
  1163. 			// Invalid arguments for BREAK command
  1164. 			#region BREAK command line
  1165.  
  1166. 			pattern = @"(^|\b|\()BREAK(\b|$)";
  1167. 			regex = new Regex( pattern, ignorecase );
  1168. 			if ( regex.IsMatch( line ) )
  1169. 			{
  1170. 				pattern = @"(?:^|\b|\()BREAK(\s+(ON|OFF))\s*$";
  1171. 				regex = new Regex( pattern, ignorecase );
  1172. 				if ( !regex.IsMatch( line ) )
  1173. 				{
  1174. 					pattern = @"(?:^|\b|\()BREAK([^\n\r&|]+)";
  1175. 					regex = new Regex( pattern, ignorecase );
  1176. 					if ( regex.IsMatch( line ) )
  1177. 					{
  1178. 						string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  1179. 						if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  1180. 						{
  1181. 							message = String.Format( "Invalid command line argument \"{0}\" for BREAK command, the only arguments accepted are ON and OFF", cla );
  1182. 							allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1183. 							errmsg += String.Format( "\n{0}.", message );
  1184. 							errinline = true;
  1185. 						}
  1186. 					}
  1187. 				}
  1188. 				message = "The BREAK command is available for backward compatibility only, it does not DO anything in Windows";
  1189. 				alltips += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1190. 				tipsmsg += String.Format( "\n{0}.", message );
  1191. 				errinline = true;
  1192. 			}
  1193.  
  1194. 			#endregion BREAK command line
  1195.  
  1196. 			// Invalid arguments for CALL command
  1197. 			#region CALL command line
  1198.  
  1199. 			pattern = @"(?:^|\b|\()CALL\s+([^\n\r\s]+)(\b|$)";
  1200. 			regex = new Regex( pattern, ignorecase );
  1201. 			if ( regex.IsMatch( line ) )
  1202. 			{
  1203. 				bool callerr = false;
  1204. 				string cla = regex.Match( line ).Groups[1].ToString( );
  1205. 				if ( String.IsNullOrWhiteSpace( cla ) )
  1206. 				{
  1207. 					callerr = true;
  1208. 				}
  1209. 				else
  1210. 				{
  1211. 					if ( cla[0] != ':' ) // CALLs to subroutine labels will be handled later
  1212. 					{
  1213. 						pattern = String.Format( @"^({0})$", intcmdpattern );
  1214. 						regex = new Regex( pattern, ignorecase );
  1215. 						if ( !regex.IsMatch( cla ) )
  1216. 						{
  1217. 							if ( String.IsNullOrWhiteSpace( Which( cla ) ) )
  1218. 							{
  1219. 								callerr = true;
  1220. 							}
  1221. 						}
  1222. 					}
  1223. 				}
  1224. 				if ( callerr )
  1225. 				{
  1226. 					message = String.Format( "Invalid command line argument \"{0}\" for CALL command: no matching internal command, nor external command was found", cla );
  1227. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1228. 					errmsg += String.Format( "\n{0}.", message );
  1229. 					errinline = true;
  1230. 				}
  1231. 			}
  1232.  
  1233. 			#endregion CALL command line
  1234.  
  1235. 			// Invalid switch for CD command
  1236. 			#region CD switches
  1237.  
  1238. 			pattern = @"(?:^|\b|\()(CD|CHDIR)\s+(/[^D][^\n\r\s/]*)";
  1239. 			regex = new Regex( pattern, ignorecase );
  1240. 			if ( regex.IsMatch( line ) )
  1241. 			{
  1242. 				string cmd = regex.Match( line ).Groups[1].ToString( );
  1243. 				string cls = regex.Match( line ).Groups[2].ToString( );
  1244. 				message = String.Format( "Invalid command line switch \"{0}\" for {1} command, the only accepted switch is /D", cls, cmd );
  1245. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1246. 				errmsg += String.Format( "\n{0}.", message );
  1247. 				errinline = true;
  1248. 			}
  1249.  
  1250. 			#endregion CD switches
  1251.  
  1252. 			// Invalid argument for DATE command
  1253. 			#region DATE arguments
  1254.  
  1255. 			pattern = @"(^|\b|\()(?<![%!])DATE(?>![%!:])(\b|$)";
  1256. 			regex = new Regex( pattern, ignorecase );
  1257. 			if ( regex.IsMatch( line ) )
  1258. 			{
  1259. 				pattern = @"(?:^|\b|\()DATE\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  1260. 				regex = new Regex( pattern, ignorecase );
  1261. 				if ( regex.IsMatch( line ) )
  1262. 				{
  1263. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1264. 					message = String.Format( "Invalid command line switch \"{0}\" for DATE command, the only valid switch is /T", cls );
  1265. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1266. 					errmsg += String.Format( "\n{0}.", message );
  1267. 					errinline = true;
  1268. 				}
  1269. 				else
  1270. 				{
  1271. 					pattern = @"(?:^|\b|\()DATE\s+(/T|(%|!)[\w_-]{1,16}\2|[\d/-]{10})";
  1272. 					regex = new Regex( pattern, ignorecase );
  1273. 					if ( !regex.IsMatch( line ) )
  1274. 					{
  1275. 						string cls = regex.Match( line ).Groups[1].ToString( );
  1276. 						message = String.Format( "Invalid command line argument \"{0}\" for DATE command, this should be a date", cls );
  1277. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1278. 						errmsg += String.Format( "\n{0}.", message );
  1279. 						errinline = true;
  1280. 					}
  1281. 				}
  1282. 			}
  1283.  
  1284. 			#endregion DATE arguments
  1285.  
  1286. 			// Invalid switch for EXIT command
  1287. 			#region EXIT switches
  1288.  
  1289. 			pattern = @"(?:^|\b|\()EXIT\s+(/[^B][^\n\r\s/]*)";
  1290. 			regex = new Regex( pattern, ignorecase );
  1291. 			if ( regex.IsMatch( line ) )
  1292. 			{
  1293. 				string cls = regex.Match( line ).Groups[1].ToString( );
  1294. 				message = String.Format( "Invalid command line switch \"{0}\" for EXIT command, the only accepted switch is /B", cls );
  1295. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1296. 				errmsg += String.Format( "\n{0}.", message );
  1297. 				errinline = true;
  1298. 			}
  1299.  
  1300. 			#endregion EXIT switches
  1301.  
  1302. 			// Invalid switch for FOR command
  1303. 			#region FOR switches
  1304.  
  1305. 			pattern = @"(?:^|\b|\()FOR\s+(/[^DFLR][^\n\r\s=/]*)";
  1306. 			regex = new Regex( pattern, ignorecase );
  1307. 			if ( regex.IsMatch( line ) )
  1308. 			{
  1309. 				message = String.Format( "Invalid command line switch \"{0}\" for FOR command, accepted switches are /D, /F, /L and /R", regex.Match( line ).Groups[1].ToString( ) );
  1310. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1311. 				errmsg += String.Format( "\n{0}.", message );
  1312. 				errinline = true;
  1313. 			}
  1314.  
  1315. 			#endregion FOR switches
  1316.  
  1317. 			// Invalid arguments for FTYPE command
  1318. 			#region FTYPE command line
  1319.  
  1320. 			pattern = @"(^|\b|\()FTYPE(\b|$)";
  1321. 			regex = new Regex( pattern, ignorecase );
  1322. 			if ( regex.IsMatch( line ) )
  1323. 			{
  1324. 				pattern = @"(?:^|\b|\()FTYPE(\s+[^\.\n\r&|][\w \.\-]{2,}(=[\w: \.\\\-\(\)]+)?)?\s*$";
  1325. 				regex = new Regex( pattern, ignorecase );
  1326. 				if ( !regex.IsMatch( line ) )
  1327. 				{
  1328. 					pattern = @"(?:^|\b|\()FTYPE\s+([^\n\r&|]+)";
  1329. 					regex = new Regex( pattern, ignorecase );
  1330. 					if ( regex.IsMatch( line ) )
  1331. 					{
  1332. 						string cla = regex.Match( line ).Groups[1].ToString( ).Trim( );
  1333. 						if ( !String.IsNullOrWhiteSpace( cla ) && cla[0] != '/' ) // No need to display a message for switches, the check for switches will handle that
  1334. 						{
  1335. 							message = String.Format( "Invalid command line argument(s) \"{0}\" for FTYPE command", cla );
  1336. 							allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1337. 							errmsg += String.Format( "\n{0}.", message );
  1338. 							errinline = true;
  1339. 						}
  1340. 					}
  1341. 				}
  1342. 			}
  1343.  
  1344. 			#endregion FTYPE command line
  1345.  
  1346. 			// Invalid switch for IF command
  1347. 			#region IF switches
  1348.  
  1349. 			pattern = @"(?:^|\b|\()IF\s+(/[^I][^\n\r\s/]*)";
  1350. 			regex = new Regex( pattern, ignorecase );
  1351. 			if ( regex.IsMatch( line ) )
  1352. 			{
  1353. 				string cls = regex.Match( line ).Groups[1].ToString( );
  1354. 				message = String.Format( "Invalid command line switch \"{0}\" for IF command, the only accepted switch is /I", cls );
  1355. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1356. 				errmsg += String.Format( "\n{0}.", message );
  1357. 				errinline = true;
  1358. 			}
  1359.  
  1360. 			#endregion IF switches
  1361.  
  1362. 			// Invalid switch for SET command
  1363. 			#region SET switches
  1364.  
  1365. 			pattern = @"(?:^|\b|\()SET\s+(/[^AP][^\n\r\s=/]*)";
  1366. 			regex = new Regex( pattern, ignorecase );
  1367. 			if ( regex.IsMatch( line ) )
  1368. 			{
  1369. 				message = String.Format( "Invalid command line switch \"{0}\" for SET command, accepted switches are /A and /P", regex.Match( line ).Groups[1].ToString( ) );
  1370. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1371. 				errmsg += String.Format( "\n{0}.", message );
  1372. 				errinline = true;
  1373. 			}
  1374.  
  1375. 			#endregion SET switches
  1376.  
  1377. 			// Invalid switch for SHIFT command
  1378. 			#region SHIFT switches
  1379.  
  1380. 			pattern = @"(?:^|\b|\()SHIFT\s+(/[^0-8][^\n\r\s/]*)";
  1381. 			regex = new Regex( pattern, ignorecase );
  1382. 			if ( regex.IsMatch( line ) )
  1383. 			{
  1384. 				string cls = regex.Match( line ).Groups[1].ToString( );
  1385. 				message = String.Format( "Invalid command line switch \"{0}\" for SHIFT command, the only accepted switch is a forward slash followed by a number from 0 to 8", cls );
  1386. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1387. 				errmsg += String.Format( "\n{0}.", message );
  1388. 				errinline = true;
  1389. 			}
  1390.  
  1391. 			#endregion SHIFT switches
  1392.  
  1393. 			// Invalid argument for TIME command
  1394. 			#region TIME arguments
  1395.  
  1396. 			pattern = @"(^|\b|\()(?<![%!])TIME(?>![%!:])(\b|$)";
  1397. 			regex = new Regex( pattern, ignorecase );
  1398. 			if ( regex.IsMatch( line ) )
  1399. 			{
  1400. 				pattern = @"(?:^|\b|\()TIME\s+(/[^T][^\^\n\r\s&|\)']*|/T[^\^\n\r\s&|\)']+)";
  1401. 				regex = new Regex( pattern, ignorecase );
  1402. 				if ( regex.IsMatch( line ) )
  1403. 				{
  1404. 					string cls = regex.Match( line ).Groups[1].ToString( );
  1405. 					message = String.Format( "Invalid command line switch \"{0}\" for TIME command, the only valid switch is /T", cls );
  1406. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1407. 					errmsg += String.Format( "\n{0}.", message );
  1408. 					errinline = true;
  1409. 				}
  1410. 				else
  1411. 				{
  1412. 					pattern = @"(?:^|\b|\()TIME\s+(/T|(%|!)[\w_-]{1,16}\2|[\d:\.,AMP\s]{4,11})";
  1413. 					regex = new Regex( pattern, ignorecase );
  1414. 					if ( !regex.IsMatch( line ) )
  1415. 					{
  1416. 						string cls = regex.Match( line ).Groups[1].ToString( );
  1417. 						message = String.Format( "Invalid command line argument \"{0}\" for TIME command, this should be a time value", cls );
  1418. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1419. 						errmsg += String.Format( "\n{0}.", message );
  1420. 						errinline = true;
  1421. 					}
  1422. 				}
  1423. 			}
  1424.  
  1425. 			#endregion TIME arguments
  1426.  
  1427. 			// Switches not allowed for ASSOC, CALL, COLOR, FTYPE, GOTO, MD, MKDIR, PUSHD, SETLOCAL
  1428. 			#region Switches not allowed
  1429.  
  1430. 			pattern = @"(?:^|\b|\()(ASSOC|CALL|COLOR|FTYPE|GOTO|MD|MKDIR|PUSHD|SETLOCAL)\s+(/[^\n\r\s=/]*)";
  1431. 			regex = new Regex( pattern, ignorecase );
  1432. 			if ( regex.IsMatch( line ) )
  1433. 			{
  1434. 				string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1435. 				string cls = regex.Match( line ).Groups[2].ToString( );
  1436. 				message = String.Format( "Switch \"{1}\" for {0} command is not valid, as {0} does not accept switches at all", cmd, cls );
  1437. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1438. 				errmsg += String.Format( "\n{0}.", message );
  1439. 				errinline = true;
  1440. 			}
  1441.  
  1442. 			#endregion Switches not allowed
  1443.  
  1444. 			// Arguments not required for CLS, ENDLOCAL, PAUSE, POPD, VER
  1445. 			#region Arguments not required
  1446.  
  1447. 			pattern = @"(?:^|\b|\()(CLS|ENDLOCAL|PAUSE|POPD|VER)\s+([^\n\r&|>]+)";
  1448. 			regex = new Regex( pattern, ignorecase );
  1449. 			if ( regex.IsMatch( line ) )
  1450. 			{
  1451. 				string cmd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1452. 				string cla = regex.Match( line ).Groups[2].ToString( );
  1453. 				message = String.Format( "Argument \"{1}\" for {0} command is not valid, as {0} does not accept arguments at all", cmd, cla );
  1454. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1455. 				errmsg += String.Format( "\n{0}.", message );
  1456. 				errinline = true;
  1457. 			}
  1458.  
  1459. 			#endregion Arguments not required
  1460. 		}
  1461.  
  1462. 		public static void CheckVulnerabilities( string line )
  1463. 		{
  1464. 			string pattern;
  1465. 			Regex regex;
  1466.  
  1467. 			// Unquoted %CD% code insertion vulnerability
  1468. 			#region Code insertion
  1469. 			if ( !incomment )
  1470. 			{
  1471. 				pattern = @"[^""%!](([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\n\r]+=[^\2\n\r]*)?\2)";
  1472. 				regex = new Regex( pattern, ignorecase );
  1473. 				if ( regex.IsMatch( line ) )
  1474. 				{
  1475. 					string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1476. 					message = String.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1477. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1478. 					errmsg += String.Format( "\n{0}.", message );
  1479. 					errinline = true;
  1480. 				}
  1481. 				else
  1482. 				{
  1483. 					pattern = @"[^""](([%!])([%!])(CD|__CD__)(:~\d+(,\d+)?|:[^\2\3\n\r]+=[^\2\3\n\r]*)?\3\2)";
  1484. 					regex = new Regex( pattern, ignorecase );
  1485. 					if ( regex.IsMatch( line ) )
  1486. 					{
  1487. 						string cd = regex.Match( line ).Groups[1].ToString( ).ToUpper( );
  1488. 						message = String.Format( "Unquoted {0} makes the code vulnerable to code insertion (see http://www.thesecurityfactory.be/command-injection-windows.html)", cd );
  1489. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1490. 						errmsg += String.Format( "\n{0}.", message );
  1491. 						errinline = true;
  1492. 					}
  1493. 				}
  1494. 			}
  1495. 			#endregion Code insertion
  1496.  
  1497. 			// SET /P vulnerability
  1498. 			#region SET /P
  1499.  
  1500. 			pattern = @"(^|\b)SET\s+/P\s+""?\w{1,16}\s*=";
  1501. 			regex = new Regex( pattern, ignorecase );
  1502. 			if ( regex.IsMatch( line ) )
  1503. 			{
  1504. 				message = "SET /P could make your code vulnerable to exploits (see http://www.robvanderwoude.com/battech_inputvalidation.php#SetP)";
  1505. 				allbadpr += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  1506. 				badprmsg += String.Format( "\n{0}.", message );
  1507. 				errinline = true;
  1508. 			}
  1509.  
  1510.  
  1511. 			#endregion SET /P
  1512. 		}
  1513.  
  1514. 		public static void CloseHTML( )
  1515. 		{
  1516. 			if ( log2html )
  1517. 			{
  1518. 				htmlstream.WriteLine( "\n</pre>" );
  1519. 				htmlstream.WriteLine( "</body>" );
  1520. 				htmlstream.Write( "</html>" );
  1521. 				htmlstream.Close( );
  1522. 				htmlopen = false;
  1523. 			}
  1524. 		}
  1525.  
  1526. 		public static void CollectLabels( )
  1527. 		{
  1528. 			string pattern;
  1529. 			Regex regex;
  1530.  
  1531. 			pattern = @"(?:^|\n|\r)\s*:([\w\.\[\]/+\-\(\)\{\}]{1,127})";
  1532. 			regex = new Regex( pattern );
  1533. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1534. 			{
  1535. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1536. 				{
  1537. 					labels.Add( match.Groups[1].ToString( ) );
  1538. 					upcaselabels.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1539. 				}
  1540. 			}
  1541. 		}
  1542.  
  1543. 		public static void CollectEnvironmentVariables( )
  1544. 		{
  1545. 			string pattern;
  1546. 			Regex regex;
  1547.  
  1548. 			// Static system environment variables
  1549. 			foreach ( string envvar in sysvarpattern.Split( '|' ) )
  1550. 			{
  1551. 				envvars.Add( envvar );
  1552. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1553. 			}
  1554.  
  1555. 			// Dynamic system environment variables
  1556. 			foreach ( string envvar in dynvarpattern.Split( '|' ) )
  1557. 			{
  1558. 				envvars.Add( envvar );
  1559. 				upcaseenvvars.Add( envvar.ToUpper( ) );
  1560. 			}
  1561.  
  1562. 			// Environment variables SET in the source code with "plain" SET statement
  1563. 			pattern = @"(?:^|\b)SET\s+(\w{1," + maxvarlen + @"})=";
  1564. 			regex = new Regex( pattern, ignorecase );
  1565. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1566. 			{
  1567. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1568. 				{
  1569. 					envvars.Add( match.Groups[1].ToString( ) );
  1570. 					upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1571. 				}
  1572. 			}
  1573.  
  1574. 			// Environment variables SET in the source code with SET /A or SET /P statement
  1575. 			pattern = @"(?:^|\b)SET\s+(?:/[AP]\s+)?""?(\w{1," + maxvarlen + @"})\s*=";
  1576. 			regex = new Regex( pattern, ignorecase );
  1577. 			if ( regex.IsMatch( allsrc_nocomments ) )
  1578. 			{
  1579. 				foreach ( Match match in regex.Matches( allsrc_nocomments ) )
  1580. 				{
  1581. 					if ( !upcaseenvvars.Contains( match.Groups[1].ToString( ).ToUpper( ) ) )
  1582. 					{
  1583. 						envvars.Add( match.Groups[1].ToString( ) );
  1584. 						upcaseenvvars.Add( match.Groups[1].ToString( ).ToUpper( ) );
  1585. 					}
  1586. 				}
  1587. 			}
  1588.  
  1589. 			// Include local environment variables if /L command line switch was used
  1590. 			if ( includelocalvars )
  1591. 			{
  1592. 				foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1593. 				{
  1594. 					if ( !upcaseenvvars.Contains( envvar ) )
  1595. 					{
  1596. 						envvars.Add( envvar );
  1597. 						upcaseenvvars.Add( envvar.ToUpper( ) );
  1598. 					}
  1599. 				}
  1600. 			}
  1601.  
  1602. 			// All loacal environment variables
  1603. 			foreach ( string envvar in Environment.GetEnvironmentVariables( ).Keys )
  1604. 			{
  1605. 				if ( !localenvvars.Contains( envvar.ToUpper( ) ) )
  1606. 				{
  1607. 					localenvvars.Add( envvar.ToUpper( ) );
  1608. 				}
  1609. 			}
  1610. 		}
  1611.  
  1612. 		public static void DisplayLineNumber( int num )
  1613. 		{
  1614. 			if ( dispnums )
  1615. 			{
  1616. 				Console.BackgroundColor = linenumbgcolor;
  1617. 				Console.ForegroundColor = linenumfgcolor;
  1618. 				if ( num == lastline )
  1619. 				{
  1620. 					Console.Write( "{0,4}", String.Empty );
  1621. 					WriteLineHTML( String.Format( "{0,4}", String.Empty ) );
  1622. 				}
  1623. 				else
  1624. 				{
  1625. 					Console.Write( "{0,4}", num );
  1626. 					WriteLineHTML( String.Format( "{0,4}", num ) );
  1627. 				}
  1628. 				Console.BackgroundColor = windowbgcolor;
  1629. 				Console.ForegroundColor = windowfgcolor;
  1630. 				Console.Write( " " );
  1631. 				lastline = num;
  1632. 			}
  1633. 		}
  1634.  
  1635. 		public static void DisplayMessagesPerLine( string line )
  1636. 		{
  1637. 			bool padline = true;
  1638. 			string blankline = new String( ' ', Console.WindowWidth );
  1639. 			string msgline;
  1640.  
  1641. 			if ( errinline )
  1642. 			{
  1643. 				if ( lasterrline != linenum )
  1644. 				{
  1645. 					lineswitherrors += 1;
  1646. 				}
  1647. 				foreach ( string error in errmsg.Split( "\n\r".ToCharArray( ) ) )
  1648. 				{
  1649. 					if ( !String.IsNullOrWhiteSpace( error ) )
  1650. 					{
  1651. 						if ( wrap )
  1652. 						{
  1653. 							msgline = WordWrap( ( error ).Replace( "\t", " ERROR:   " ), padline );
  1654. 						}
  1655. 						else
  1656. 						{
  1657. 							msgline = ( error + blankline ).Replace( "\t", " ERROR:   " ).Substring( 0, Console.WindowWidth - 1 );
  1658. 						}
  1659. 						Console.BackgroundColor = warningbgcolor;
  1660. 						Console.ForegroundColor = warningfgcolor;
  1661. 						Console.Write( msgline );
  1662. 						WriteLineHTML( msgline );
  1663. 						Console.ResetColor( );
  1664. 						Console.WriteLine( );
  1665. 					}
  1666. 				}
  1667. 				foreach ( string badpractice in badprmsg.Split( '\n' ) )
  1668. 				{
  1669. 					if ( !String.IsNullOrWhiteSpace( badpractice ) )
  1670. 					{
  1671. 						if ( wrap )
  1672. 						{
  1673. 							msgline = WordWrap( ( badpractice ).Replace( "\t", " WARNING: " ), padline );
  1674. 						}
  1675. 						else
  1676. 						{
  1677. 							msgline = ( badpractice + blankline ).Replace( "\t", " WARNING: " ).Substring( 0, Console.WindowWidth - 1 );
  1678. 						}
  1679. 						Console.BackgroundColor = badprbgcolor;
  1680. 						Console.ForegroundColor = badprfgcolor;
  1681. 						Console.Write( msgline );
  1682. 						WriteLineHTML( msgline );
  1683. 						Console.ResetColor( );
  1684. 						Console.WriteLine( );
  1685. 					}
  1686. 				}
  1687. 				foreach ( string tip in tipsmsg.Split( '\n' ) )
  1688. 				{
  1689. 					if ( !String.IsNullOrWhiteSpace( tip ) )
  1690. 					{
  1691. 						if ( wrap )
  1692. 						{
  1693. 							msgline = WordWrap( ( tip ).Replace( "\t", " TIP:     " ), padline );
  1694. 						}
  1695. 						else
  1696. 						{
  1697. 							msgline = ( tip + blankline ).Replace( "\t", " TIP:     " ).Substring( 0, Console.WindowWidth - 1 );
  1698. 						}
  1699. 						Console.BackgroundColor = tipsbgcolor;
  1700. 						Console.ForegroundColor = tipsfgcolor;
  1701. 						Console.Write( msgline );
  1702. 						WriteLineHTML( msgline );
  1703. 						Console.ResetColor( );
  1704. 						Console.WriteLine( );
  1705. 					}
  1706. 				}
  1707. 				errinline = false;
  1708. 				lasterrline = linenum;
  1709. 				rc = linenum;
  1710. 			}
  1711. 		}
  1712.  
  1713. 		public static void DisplayMessagesSummary( )
  1714. 		{
  1715. 			// Summary
  1716. 			if ( lineswitherrors == 1 )
  1717. 			{
  1718. 				allerrsum += "\n1   line generated a warning and should be examined";
  1719. 			}
  1720. 			if ( lineswitherrors > 1 )
  1721. 			{
  1722. 				allerrsum += String.Format( "\n{0,-3} lines generated warnings and should be examined", lineswitherrors );
  1723. 			}
  1724. 			// Unterminated %
  1725. 			if ( unterminatedpercentsigns == 1 )
  1726. 			{
  1727. 				allerrsum += "\n1   unterminated percent sign was found";
  1728. 			}
  1729. 			if ( unterminatedpercentsigns > 1 )
  1730. 			{
  1731. 				allerrsum += String.Format( "\n{0,-3} unterminated percent signs were found", unterminatedpercentsigns );
  1732. 			}
  1733. 			// Unterminated !
  1734. 			if ( unterminatedexclamations == 1 )
  1735. 			{
  1736. 				allerrsum += "\n1   unterminated exclamation mark was found while delayed variable expansion was enabled";
  1737. 			}
  1738. 			if ( unterminatedexclamations > 1 )
  1739. 			{
  1740. 				allerrsum += String.Format( "\n{0,-3} unterminated exclamation marks were found while delayed variable expansion was enabled", unterminatedexclamations );
  1741. 			}
  1742. 			// Unterminated "
  1743. 			if ( unterminateddoublequotes == 1 )
  1744. 			{
  1745. 				allerrsum += String.Format( "\n1   unterminated doublequote was found", unterminateddoublequotes );
  1746. 			}
  1747. 			if ( unterminateddoublequotes > 1 )
  1748. 			{
  1749. 				allerrsum += String.Format( "\n{0,-3} unterminated doublequotes were found", unterminateddoublequotes );
  1750. 			}
  1751. 			// Unterminated '
  1752. 			if ( unterminatedsinglequotes == 1 )
  1753. 			{
  1754. 				allerrsum += String.Format( "\n1   unterminated singlequote was found in a FOR /F loop", unterminatedsinglequotes );
  1755. 			}
  1756. 			if ( unterminatedsinglequotes > 1 )
  1757. 			{
  1758. 				allerrsum += String.Format( "\n{0,-3} unterminated singlequotes were found in FOR /F loops", unterminatedsinglequotes );
  1759. 			}
  1760. 			// Undefined environment variables
  1761. 			string ignoredmsg = ( includelocalvars ? String.Empty : " (ignoring local environment variables)" );
  1762. 			if ( envvarerrors.Count == 1 )
  1763. 			{
  1764. 				allerrsum += String.Format( "\n1   undefined variable was detected{0}: {1}", ignoredmsg, envvarerrors[0] );
  1765. 			}
  1766. 			if ( envvarerrors.Count > 1 )
  1767. 			{
  1768. 				allerrsum += String.Format( "\n{0,-3} undefined variables were detected{1}: {2}", envvarerrors.Count, ignoredmsg, String.Join( ", ", envvarerrors ) );
  1769. 			}
  1770. 			// Undefined labels
  1771. 			if ( gotoerrors.Count == 1 )
  1772. 			{
  1773. 				allerrsum += String.Format( "\n1   label was missing: {1}", gotoerrors[0] );
  1774. 			}
  1775. 			if ( gotoerrors.Count > 1 )
  1776. 			{
  1777. 				allerrsum += String.Format( "\n{0,-3} labels were missing: {1}", gotoerrors.Count, String.Join( ", ", gotoerrors ) );
  1778. 			}
  1779. 			// Undefined subroutines
  1780. 			if ( callerrors.Count == 1 )
  1781. 			{
  1782. 				allerrsum += String.Format( "\n1   subroutine label was missing (note that subroutine labels are case sensitive): {0}", callerrors[0] );
  1783. 			}
  1784. 			if ( callerrors.Count > 1 )
  1785. 			{
  1786. 				allerrsum += String.Format( "\n{0,-3} subroutine labels were missing (note that subroutine labels are case sensitive): {1}", callerrors.Count, String.Join( ", ", callerrors ) );
  1787. 			}
  1788. 			// Obsolete commands
  1789. 			if ( obsoletecmd == 1 )
  1790. 			{
  1791. 				allerrsum += "\n1   obsolete or 16-bit command was found";
  1792. 			}
  1793. 			if ( obsoletecmd > 1 )
  1794. 			{
  1795. 				allerrsum += String.Format( "\n{0,-3} obsolete and/or 16-bit commands were found", obsoletecmd );
  1796. 			}
  1797. 			// Unterminated parentheses
  1798. 			if ( parenthesis > 0 )
  1799. 			{
  1800. 				allerrsum += "\nOpening parenthesis outnumber closing parenthesis by " + parenthesis;
  1801. 			}
  1802. 			if ( parenthesis < 0 )
  1803. 			{
  1804. 				allerrsum += "\nClosing parenthesis outnumber opening parenthesis by " + Math.Abs( parenthesis );
  1805. 			}
  1806. 			// Unterminated SETLOCALs
  1807. 			if ( setlocal > 0 )
  1808. 			{
  1809. 				allerrsum += "\nSETLOCALs outnumber ENDLOCALs by " + setlocal;
  1810. 			}
  1811. 			if ( setlocal < 0 )
  1812. 			{
  1813. 				allerrsum += "\nENDLOCALs outnumber SETLOCALs by " + Math.Abs( setlocal );
  1814. 			}
  1815.  
  1816. 			// Display warnings if applicable
  1817. 			if ( !String.IsNullOrWhiteSpace( allerrors ) )
  1818. 			{
  1819. 				allerrors = String.Format( "\nERRORS / VULNERABILITIES:{0}\n", allerrors );
  1820. 			}
  1821. 			if ( !String.IsNullOrWhiteSpace( allbadpr ) )
  1822. 			{
  1823. 				allerrors = String.Format( "{0}\nWARNINGS / TO BE INVESTIGATED:{1}\n", allerrors, allbadpr );
  1824. 			}
  1825. 			if ( !String.IsNullOrWhiteSpace( alltips ) )
  1826. 			{
  1827. 				allerrors = String.Format( "{0}\nTIPS / BEST PRACTICES:{1}\n", allerrors, alltips );
  1828. 			}
  1829. 			if ( !String.IsNullOrWhiteSpace( allerrsum ) )
  1830. 			{
  1831. 				allerrors = String.Format( "{0}\nSUMMARY:{1}\n", allerrors, allerrsum );
  1832. 			}
  1833.  
  1834. 			if ( !String.IsNullOrEmpty( allerrors ) )
  1835. 			{
  1836. 				allerrors += "\n\nNote that some warnings are only displayed once. Correct the errors and run this test again.";
  1837. 				Console.ResetColor( );
  1838. 				Console.WriteLine( "\n" );
  1839. 				Console.WriteLine( "Time      :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1840. 				Console.WriteLine( "Program   :  BatCodeCheck, Version {0}", progver );
  1841. 				Console.WriteLine( "Arguments :  {0}", allargs );
  1842. 				Console.WriteLine( "File name :  \"{0}\"", Path.GetFullPath( source ) );
  1843. 				Console.WriteLine( "File date :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1844. 				if ( envvarerrors.Count > 0 )
  1845. 				{
  1846. 					Console.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  1847. 				}
  1848. 				Console.WriteLine( );
  1849. 				Console.BackgroundColor = reportbgcolor;
  1850. 				Console.ForegroundColor = reportfgcolor;
  1851. 				if ( wrap )
  1852. 				{
  1853. 					string[] all_errors = allerrors.Split( "\n\r".ToCharArray( ) );
  1854. 					foreach ( string all_err in all_errors )
  1855. 					{
  1856. 						Console.WriteLine( WordWrap( all_err, false ) );
  1857. 					}
  1858. 				}
  1859. 				else
  1860. 				{
  1861. 					Console.WriteLine( allerrors );
  1862. 				}
  1863. 				WriteLineHTML( allerrors );
  1864. 				Console.ResetColor( );
  1865. 				Console.WriteLine( );
  1866. 				rc = Math.Max( 1, rc );
  1867. 				if ( logging )
  1868. 				{
  1869. 					StreamWriter log = new StreamWriter( logfile, !replacelog );
  1870. 					log.WriteLine( "Time      :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1871. 					log.WriteLine( "Program   :  BatCodeCheck, Version {0}", progver );
  1872. 					log.WriteLine( "Arguments :  {0}", allargs );
  1873. 					log.WriteLine( "File name :  \"{0}\"", Path.GetFullPath( source ) );
  1874. 					log.WriteLine( "File date :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1875. 					if ( envvarerrors.Count > 0 )
  1876. 					{
  1877. 						log.WriteLine( "Local environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  1878. 					}
  1879. 					log.WriteLine( allerrors + "\n\n" );
  1880. 					log.Close( );
  1881. 				}
  1882. 			}
  1883. 		}
  1884.  
  1885. 		public static string EscapedHTML( char chr )
  1886. 		{
  1887. 			switch ( chr )
  1888. 			{
  1889. 				case '&':
  1890. 					return "&amp;";
  1891. 				case '>':
  1892. 					return "&gt;";
  1893. 				case '<':
  1894. 					return "&lt;";
  1895. 				default:
  1896. 					return String.Format( "{0}", chr );
  1897. 			}
  1898. 		}
  1899.  
  1900. 		public static void OpenHTML( )
  1901. 		{
  1902. 			if ( log2html )
  1903. 			{
  1904. 				htmlstream = new StreamWriter( htmlfile );
  1905. 				htmlopen = true;
  1906. 				htmlstream.WriteLine( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
  1907. 				htmlstream.WriteLine( "<html>" );
  1908. 				htmlstream.WriteLine( "<head>" );
  1909. 				htmlstream.WriteLine( "<title>BatCodeCheck Version {0} report for {1}</title>", progver, Path.GetFileName( source ) );
  1910. 				htmlstream.WriteLine( "<style type=\"text/css\">" );
  1911. 				htmlstream.WriteLine( "body" );
  1912. 				htmlstream.WriteLine( "{" );
  1913. 				htmlstream.WriteLine( "\tbackground-color: {0};", windowbgcolor.ToString( ).ToLower( ) );
  1914. 				htmlstream.WriteLine( "\tcolor: {0};", windowfgcolor.ToString( ).ToLower( ) );
  1915. 				htmlstream.WriteLine( "}" );
  1916. 				htmlstream.WriteLine( "</style>" );
  1917. 				htmlstream.WriteLine( "</head>" );
  1918. 				htmlstream.WriteLine( "<body>" );
  1919. 				htmlstream.WriteLine( "<pre>" );
  1920. 				htmlstream.WriteLine( "<span style=\"color: white;\">Time      :  {0}", DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1921. 				htmlstream.WriteLine( "Program   :  BatCodeCheck, Version {0}", progver );
  1922. 				htmlstream.WriteLine( "Arguments :  {0}", allargs );
  1923. 				htmlstream.WriteLine( "File name :  \"{0}\"", Path.GetFullPath( source ) );
  1924. 				htmlstream.Write( "File date :  {0}", File.GetLastWriteTime( source ).ToString( "yyyy-MM-dd HH:mm:ss" ) );
  1925. 				if ( envvarerrors.Count > 0 )
  1926. 				{
  1927. 					htmlstream.Write( "\nLocal environment variables are {0}", ( includelocalvars ? "included" : "ignored" ) );
  1928. 				}
  1929. 				htmlstream.WriteLine( "</span>\n" );
  1930. 			}
  1931. 		}
  1932.  
  1933. 		public static void ParseLine( string line )
  1934. 		{
  1935. 			ResetInlineVariables( );
  1936. 			DisplayLineNumber( linenum );
  1937. 			CheckCommonMistakes( line );
  1938. 			CheckCommandLineArguments( line );
  1939. 			CheckBadPractices( line );
  1940. 			CheckBestPractices( line );
  1941. 			ParseLineAndHighlight( line );
  1942. 			CheckVulnerabilities( line );
  1943. 			DisplayMessagesPerLine( line );
  1944. 		}
  1945.  
  1946. 		public static void ParseLineAndHighlight( string line )
  1947. 		{
  1948. 			// Comments are highlighted and not interpreted at all.
  1949. 			// This may spell trouble in case of multiple commands on a single line.
  1950. 			if ( new Regex( @"^\s*(REM($|\s)|::)", ignorecase ).IsMatch( line ) )
  1951. 			{
  1952. 				Console.ForegroundColor = commentfgscolor;
  1953. 				Console.WriteLine( line );
  1954. 				WriteHTML( line );
  1955. 				Console.ResetColor( );
  1956. 			}
  1957. 			// do not highlight ECHO ON or ECHO OFF as ECHOed text
  1958. 			else if ( new Regex( @"^\s*@?ECHO\s+(ON|OFF)\s*$", ignorecase ).IsMatch( line ) )
  1959. 			{
  1960. 				Console.ResetColor( );
  1961. 				Console.WriteLine( line );
  1962. 				WriteHTML( line );
  1963. 			}
  1964. 			// check if delayed variable expansion is enabled (will choke on nested SETLOCALs)
  1965. 			else if ( new Regex( @"^\s*SETLOCAL\s+((EN|DIS)ABLEEXTENSIONS\s+)?ENABLEDELAYEDEXPANSION(\b|$)", ignorecase ).IsMatch( line ) )
  1966. 			{
  1967. 				delayedvarexp = true;
  1968. 				delayedvarglobal = true;
  1969. 				Console.ResetColor( );
  1970. 				Console.WriteLine( line );
  1971. 				WriteHTML( line );
  1972. 			}
  1973. 			// check if delayed variable expansion is disabled (will choke on nested SETLOCALs)
  1974. 			else if ( new Regex( @"^\s*ENDLOCAL\b", ignorecase ).IsMatch( line ) )
  1975. 			{
  1976. 				delayedvarexp = false;
  1977. 				Console.ResetColor( );
  1978. 				Console.WriteLine( line );
  1979. 				WriteHTML( line );
  1980. 			}
  1981. 			else
  1982. 			{
  1983. 				ParseLinePerCharacter( line );
  1984. 			}
  1985. 		}
  1986.  
  1987. 		public static void ParseLinePerCharacter( string line )
  1988. 		{
  1989. 			#region Initialize Inline Variables
  1990.  
  1991. 			//string badprmsg = String.Empty;
  1992. 			//string errmsg = String.Empty;
  1993. 			//string tipsmsg = String.Empty;
  1994. 			//string message = String.Empty;
  1995. 			string part = String.Empty;
  1996. 			string buildword = String.Empty;
  1997. 			string lastword = String.Empty;
  1998. 			int doublequotes = 0;
  1999. 			int singlequotes = 0;
  2000. 			bool doublepercent = false;
  2001. 			bool doublequoted = false;
  2002. 			bool echoed = false;
  2003. 			bool escnext = false;
  2004. 			bool forfloop = false;
  2005. 			bool inexcl = false;
  2006. 			bool inperc = false;
  2007. 			bool isparenthesis = false;
  2008. 			bool redirection = false;
  2009. 			bool singlequoted = false;
  2010. 			bool tempdblquoted = false;
  2011. 			bool tempinexcl = false;
  2012. 			bool tempinperc = false;
  2013. 			bool tempsngquoted = false;
  2014. 			bool tildestr = false;
  2015.  
  2016. 			#endregion Initialize Inline Variables
  2017.  
  2018. 			// check if inside a FOR /F loop
  2019. 			forfloop = new Regex( @"\bFOR\s+/F\b", ignorecase ).IsMatch( line );
  2020.  
  2021. 			char prevchr = ' ';
  2022. 			int index = -1;
  2023.  
  2024. 			foreach ( char chr in line )
  2025. 			{
  2026. 				index += 1;
  2027. 				switch ( chr )
  2028. 				{
  2029. 					case '"':
  2030. 						doublequoted = ( doublequoted ^ !escnext ) && !echoed;
  2031. 						if ( !echoed )
  2032. 						{
  2033. 							doublequotes += 1;
  2034. 						}
  2035. 						escnext = false;
  2036. 						inperc = false;
  2037. 						redirection = false;
  2038. 						tildestr = false;
  2039. 						break;
  2040. 					case (char) 39:
  2041. 						singlequoted = ( !singlequoted ^ escnext ) && !echoed;
  2042. 						if ( !echoed && ( doublequotes % 2 == 0 ) )
  2043. 						{
  2044. 							singlequotes += 1;
  2045. 						}
  2046. 						escnext = false;
  2047. 						inperc = false;
  2048. 						redirection = false;
  2049. 						tildestr = false;
  2050. 						break;
  2051. 					case '^':
  2052. 						escnext = ( prevchr != chr );
  2053. 						inperc = false;
  2054. 						tildestr = false;
  2055. 						break;
  2056. 					case '%':
  2057. 						if ( prevchr == '%' )
  2058. 						{
  2059. 							doublepercent = true;
  2060. 						}
  2061. 						else
  2062. 						{
  2063. 							doublepercent = false;
  2064. 							inperc = !inperc;
  2065. 						}
  2066. 						escnext = false;
  2067. 						redirection = false;
  2068. 						tildestr = false;
  2069. 						break;
  2070. 					case '!':
  2071. 						if ( delayedvarexp )
  2072. 						{
  2073. 							inexcl = !inexcl;
  2074. 						}
  2075. 						escnext = false;
  2076. 						inperc = false;
  2077. 						redirection = false;
  2078. 						tildestr = false;
  2079. 						break;
  2080. 					case '~':
  2081. 						if ( prevchr == '%' )
  2082. 						{
  2083. 							inperc = false;
  2084. 							tildestr = true;
  2085. 						}
  2086. 						escnext = false;
  2087. 						redirection = false;
  2088. 						break;
  2089. 					case '&':
  2090. 						if ( prevchr == chr )
  2091. 						{
  2092. 							doublequoted = false;
  2093. 							echoed = false;
  2094. 							inperc = false;
  2095. 							redirection = false;
  2096. 						}
  2097. 						else
  2098. 						{
  2099. 							redirection = !escnext;
  2100. 						}
  2101. 						if ( singlequoted && redirection )
  2102. 						{
  2103. 							message = "Unescaped ampersand(s) in singlequoted text (FOR /F loop" + ( forfloop ? String.Empty : "?" ) + ")";
  2104. 							if ( errmsg.IndexOf( message ) > -1 )
  2105. 							{
  2106. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2107. 								errmsg += String.Format( "\n{0}.", message );
  2108. 							}
  2109. 							errinline = true;
  2110. 						}
  2111. 						escnext = false;
  2112. 						inexcl = false;
  2113. 						inperc = false;
  2114. 						tildestr = false;
  2115. 						break;
  2116. 					case '|':
  2117. 						if ( prevchr == chr )
  2118. 						{
  2119. 							doublequoted = false;
  2120. 							echoed = false;
  2121. 							inperc = false;
  2122. 							redirection = false;
  2123. 						}
  2124. 						else
  2125. 						{
  2126. 							redirection = !escnext;
  2127. 						}
  2128. 						if ( singlequoted && redirection )
  2129. 						{
  2130. 							message = "Unescaped pipe symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? String.Empty : "?" ) + ")";
  2131. 							if ( errmsg.IndexOf( message ) > -1 )
  2132. 							{
  2133. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2134. 								errmsg += String.Format( "\n{0}.", message );
  2135. 							}
  2136. 							errinline = true;
  2137. 						}
  2138. 						escnext = false;
  2139. 						echoed = false;
  2140. 						incomment = false;
  2141. 						inexcl = false;
  2142. 						inperc = false;
  2143. 						tildestr = false;
  2144. 						break;
  2145. 					case '>':
  2146. 						redirection = !escnext;
  2147. 						if ( singlequoted && redirection )
  2148. 						{
  2149. 							message = "Unescaped \"greater than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? String.Empty : "?" ) + ")";
  2150. 							if ( errmsg.IndexOf( message ) > -1 )
  2151. 							{
  2152. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2153. 								errmsg += String.Format( "\n{0}.", message );
  2154. 							}
  2155. 							errinline = true;
  2156. 						}
  2157. 						escnext = false;
  2158. 						echoed = false;
  2159. 						incomment = false;
  2160. 						inexcl = false;
  2161. 						inperc = false;
  2162. 						tildestr = false;
  2163. 						break;
  2164. 					case '<':
  2165. 						redirection = !escnext;
  2166. 						if ( singlequoted && redirection )
  2167. 						{
  2168. 							message = "Unescaped \"less than\" symbol(s) in singlequoted text (FOR /F loop" + ( forfloop ? String.Empty : "?" ) + ")";
  2169. 							if ( errmsg.IndexOf( message ) > -1 )
  2170. 							{
  2171. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2172. 								errmsg += String.Format( "\n{0}.", message );
  2173. 							}
  2174. 							errinline = true;
  2175. 						}
  2176. 						escnext = false;
  2177. 						inexcl = false;
  2178. 						inperc = false;
  2179. 						tildestr = false;
  2180. 						break;
  2181. 					case ' ':
  2182. 					case '\t':
  2183. 						if ( inexcl )
  2184. 						{
  2185. 							unterminatedexclamations += 1;
  2186. 							message = "Unterminated exclamation marks or whitespace in (delayed) variable name";
  2187. 							if ( errmsg.IndexOf( message ) > -1 )
  2188. 							{
  2189. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2190. 								errmsg += String.Format( "\n{0}.", message );
  2191. 							}
  2192. 							errinline = true;
  2193. 						}
  2194. 						if ( inperc )
  2195. 							escnext = false;
  2196. 						inexcl = false;
  2197. 						inperc = false;
  2198. 						redirection = false;
  2199. 						tildestr = false;
  2200. 						break;
  2201. 					case '(':
  2202. 						if ( !echoed )
  2203. 						{
  2204. 							parenthesis += 1;
  2205. 						}
  2206. 						inexcl = false;
  2207. 						inperc = false;
  2208. 						isparenthesis = true;
  2209. 						redirection = false;
  2210. 						tildestr = false;
  2211. 						break;
  2212. 					case ')':
  2213. 						if ( !echoed )
  2214. 						{
  2215. 							parenthesis -= 1;
  2216. 						}
  2217. 						if ( parenthesis < 0 )
  2218. 						{
  2219. 							message = "Too many closing parenthesis";
  2220. 							if ( allerrors.IndexOf( message ) == -1 )
  2221. 							{
  2222. 								allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2223. 								errmsg += String.Format( "\n{0}.", message );
  2224. 								errinline = true;
  2225. 							}
  2226. 						}
  2227. 						inexcl = false;
  2228. 						inperc = false;
  2229. 						isparenthesis = true;
  2230. 						redirection = false;
  2231. 						tildestr = false;
  2232. 						break;
  2233. 					case 'a':
  2234. 					case 'd':
  2235. 					case 'f':
  2236. 					case 'n':
  2237. 					case 'p':
  2238. 					case 's':
  2239. 					case 't':
  2240. 					case 'x':
  2241. 					case 'z':
  2242. 						// "$PATH:" is ignored in the highlighting
  2243. 						if ( ( prevchr == '%' ) || tildestr )
  2244. 						{
  2245. 							inperc = false;
  2246. 							tildestr = true;
  2247. 						}
  2248. 						escnext = false;
  2249. 						redirection = false;
  2250. 						break;
  2251. 					case '0':
  2252. 					case '1':
  2253. 					case '2':
  2254. 					case '3':
  2255. 					case '4':
  2256. 					case '5':
  2257. 					case '6':
  2258. 					case '7':
  2259. 					case '8':
  2260. 					case '9':
  2261. 						if ( ( prevchr == '%' ) || tildestr )
  2262. 						{
  2263. 							inperc = false;
  2264. 							tildestr = true;
  2265. 						}
  2266. 						escnext = false;
  2267. 						redirection = false;
  2268. 						break;
  2269. 					default:
  2270. 						escnext = false;
  2271. 						redirection = false;
  2272. 						tildestr = false;
  2273. 						break;
  2274. 				}
  2275.  
  2276. 				// check for unescaped redirection symbols in FOR /F loops
  2277. 				if ( redirection && singlequoted && ( parenthesis % 2 == 1 ) )
  2278. 				{
  2279. 					message = "Unescaped redirection symbol(s) in single quoted text (FOR /F loop?)";
  2280. 					if ( errmsg.IndexOf( message ) > -1 )
  2281. 					{
  2282. 						allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2283. 						errmsg += String.Format( "\n{0}.", message );
  2284. 					}
  2285. 					errinline = true;
  2286. 				}
  2287.  
  2288. 				// check if we are dealing with ECHOed text or comments
  2289. 				if ( chr == ' ' || chr == '\t' )
  2290. 				{
  2291. 					lastword = buildword.ToUpper( ).Trim( );
  2292. 					buildword = String.Empty;
  2293. 				}
  2294. 				else
  2295. 				{
  2296. 					buildword += chr.ToString( );
  2297. 				}
  2298. 				if ( lastword == "ECHO" || lastword == "@ECHO" )
  2299. 				{
  2300. 					echoed = true;
  2301. 				}
  2302. 				else if ( lastword == "REM" || lastword == "::" )
  2303. 				{
  2304. 					incomment = true;
  2305. 				}
  2306.  
  2307. 				// as soon as an unescaped redirection symbol is encountered, we no longer have ECHOed text
  2308. 				if ( redirection )
  2309. 				{
  2310. 					echoed = false;
  2311. 				}
  2312.  
  2313. 				// correctly highlight the character we just parsed
  2314. 				if ( redirection )
  2315. 				{
  2316. 					// redirection symbols and parenthesis are highlighted in red
  2317. 					Console.ForegroundColor = redirectionfgcolor;
  2318. 				}
  2319. 				else if ( isparenthesis && !echoed )
  2320. 				{
  2321. 					// redirection symbols and parenthesis are highlighted in red
  2322. 					Console.ForegroundColor = parenthesisfgcolor;
  2323. 				}
  2324. 				else if ( ( ( prevchr == (char) 39 ) && ( chr == ')' ) ) || singlequoted || tempsngquoted )
  2325. 				{
  2326. 					// singlequoted text in parenthesis is highlighted in dark gray
  2327. 					Console.ForegroundColor = singlequotedfgcolor;
  2328. 				}
  2329. 				else if ( inexcl || inperc || tildestr || doublepercent || tempinexcl || tempinperc )
  2330. 				{
  2331. 					// variables and parameters are highlighted in green
  2332. 					Console.ForegroundColor = variablesfgcolor;
  2333. 				}
  2334. 				else if ( echoed )
  2335. 				{
  2336. 					// ECHOed text is highlighted in cyan, with the exception of variables
  2337. 					Console.ForegroundColor = echoedtextfgcolor;
  2338. 				}
  2339. 				else if ( doublequoted || tempdblquoted )
  2340. 				{
  2341. 					Console.ForegroundColor = doublequotedfgcolor;
  2342. 				}
  2343. 				else
  2344. 				{
  2345. 					Console.ResetColor( );
  2346. 				}
  2347. 				Console.Write( chr );
  2348. 				WriteHTML( EscapedHTML( chr ) );
  2349. 				switch ( chr )
  2350. 				{
  2351. 					case '"':
  2352. 						tempdblquoted = doublequoted;
  2353. 						redirection = false;
  2354. 						tildestr = false;
  2355. 						break;
  2356. 					case (char) 39:
  2357. 						tempsngquoted = singlequoted;
  2358. 						redirection = false;
  2359. 						tildestr = false;
  2360. 						break;
  2361. 					case '%':
  2362. 						tempinperc = inperc;
  2363. 						redirection = false;
  2364. 						break;
  2365. 					case '!':
  2366. 						if ( prevchr != '^' )
  2367. 						{
  2368. 							tempinexcl = inexcl;
  2369. 							redirection = false;
  2370. 						}
  2371. 						break;
  2372. 					case '^':
  2373. 						if ( prevchr == chr )
  2374. 						{
  2375. 							prevchr = ' ';
  2376. 						}
  2377. 						escnext = false;
  2378. 						tildestr = false;
  2379. 						break;
  2380. 					case '&':
  2381. 					case '|':
  2382. 					case '>':
  2383. 					case '<':
  2384. 						redirection = true;
  2385. 						escnext = false;
  2386. 						tildestr = false;
  2387. 						break;
  2388. 					case '0':
  2389. 					case '1':
  2390. 					case '2':
  2391. 					case '3':
  2392. 					case '4':
  2393. 					case '5':
  2394. 					case '6':
  2395. 					case '7':
  2396. 					case '8':
  2397. 					case '9':
  2398. 						if ( inperc && tildestr )
  2399. 						{
  2400. 							inperc = false;
  2401. 						}
  2402. 						tildestr = false;
  2403. 						break;
  2404. 					case ' ':
  2405. 					case '\t':
  2406. 					case '.':
  2407. 						tildestr = false;
  2408. 						break;
  2409. 					default:
  2410. 						redirection = false;
  2411. 						break;
  2412. 				}
  2413. 				isparenthesis = false;
  2414. 				prevchr = chr;
  2415. 			}
  2416. 			Console.WriteLine( );
  2417. 			if ( doublequotes % 2 == 1 )
  2418. 			{
  2419. 				unterminateddoublequotes += 1;
  2420. 				message = "Unterminated doublequotes";
  2421. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2422. 				errmsg += String.Format( "\n{0}.", message );
  2423. 				errinline = true;
  2424. 			}
  2425. 			if ( singlequotes % 2 == 1 )
  2426. 			{
  2427. 				unterminatedsinglequotes += 1;
  2428. 				message = "Unterminated singlequotes";
  2429. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2430. 				errmsg += String.Format( "\n{0}.", message );
  2431. 				errinline = true;
  2432. 			}
  2433. 			if ( inperc && !tildestr )
  2434. 			{
  2435. 				unterminatedpercentsigns += 1;
  2436. 				message = "Unterminated percent signs";
  2437. 				allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2438. 				errmsg += String.Format( "\n{0}.", message );
  2439. 				errinline = true;
  2440. 			}
  2441. 			if ( inexcl && delayedvarexp )
  2442. 			{
  2443. 				int exclmt = new Regex( @"!" ).Matches( line ).Count;
  2444. 				int escexcl = new Regex( @"\^\^!" ).Matches( line ).Count; // Exclude escaped exclamation marks
  2445. 				if ( ( exclmt - escexcl ) % 2 == 1 )
  2446. 				{
  2447. 					unterminatedexclamations += 1;
  2448. 					message = "Unterminated exclamation marks";
  2449. 					allerrors += String.Format( "\nLine {1,5:0}:\t{0}", message, linenum );
  2450. 					errmsg += String.Format( "\n{0}.", message );
  2451. 					errinline = true;
  2452. 				}
  2453. 			}
  2454. 		}
  2455.  
  2456. 		public static void ReadSource( string file )
  2457. 		{
  2458. 			StreamReader src = new StreamReader( file );
  2459. 			// Read batch source as pure ASCII, which is an absolute requirement for batch files
  2460. 			allsrc = Encoding.ASCII.GetString( Encoding.ASCII.GetBytes( src.ReadToEnd( ) ) );
  2461. 			src.Close( );
  2462. 			Regex rxcmnt = new Regex( @"(?:^|\n|\r|\(|&)\s*((?:REM|::)[^\n\r]+)", ignorecase );
  2463. 			allsrc_nocomments = rxcmnt.Replace( allsrc, String.Empty );
  2464. 		}
  2465.  
  2466. 		public static void ResetInlineVariables( )
  2467. 		{
  2468. 			badprmsg = String.Empty;
  2469. 			errinline = false;
  2470. 			errmsg = String.Empty;
  2471. 			tipsmsg = String.Empty;
  2472. 			message = String.Empty;
  2473. 			part = String.Empty;
  2474. 			buildword = String.Empty;
  2475. 			lastword = String.Empty;
  2476. 		}
  2477.  
  2478. 		public static void RestoreConsoleColors( )
  2479. 		{
  2480. 			Console.ForegroundColor = windowfgcolor;
  2481. 			Console.BackgroundColor = windowbgcolor;
  2482. 		}
  2483.  
  2484. 		public static string Which( string prog )
  2485. 		{
  2486. 			bool found = false;
  2487. 			foreach ( string folder in path )
  2488. 			{
  2489. 				if ( !found )
  2490. 				{
  2491. 					if ( !string.IsNullOrWhiteSpace( folder ) )
  2492. 					{
  2493. 						string dir = ( folder + "\\" ).Replace( "\\\\", "\\" );
  2494. 						foreach ( string ext in pathext )
  2495. 						{
  2496. 							if ( !found )
  2497. 							{
  2498. 								// The EXTERNAL program FILE to be searched MUST have an extension, either
  2499. 								// specified on the command line or one of the extensions listed in PATHEXT.
  2500. 								if ( ( prog + ext ).IndexOf( '.' ) > -1 )
  2501. 								{
  2502. 									if ( File.Exists( dir + prog + ext ) )
  2503. 									{
  2504. 										return dir + prog + ext;
  2505. 									}
  2506. 								}
  2507. 							}
  2508. 						}
  2509. 					}
  2510. 				}
  2511. 			}
  2512. 			return String.Empty;
  2513. 		}
  2514.  
  2515. 		public static string WordWrap( string input, bool padright = false )
  2516. 		{
  2517. 			string output = String.Empty;
  2518. 			string blankline = new String( ' ', Console.WindowWidth );
  2519. 			while ( !String.IsNullOrEmpty( input ) )
  2520. 			{
  2521. 				int brpos;
  2522. 				if ( input.Length < Console.WindowWidth )
  2523. 				{
  2524. 					if ( padright )
  2525. 					{
  2526. 						input = ( input + blankline ).Substring( 0, Console.WindowWidth - 1 );
  2527. 						brpos = Console.WindowWidth - 1;
  2528. 					}
  2529. 					else
  2530. 					{
  2531. 						brpos = input.Length;
  2532. 					}
  2533. 				}
  2534. 				else
  2535. 				{
  2536. 					brpos = input.Substring( 0, Math.Min( Console.WindowWidth - 1, input.Length ) ).LastIndexOfAny( ". -".ToCharArray( ) );
  2537. 				}
  2538. 				// Just break at window width if word length exceeds window width
  2539. 				if ( brpos == -1 )
  2540. 				{
  2541. 					brpos = Math.Min( Console.WindowWidth - 1, input.Length );
  2542. 				}
  2543. 				string msgline = input.Substring( 0, Math.Min( brpos + 1, input.Length ) ).Trim( );
  2544. 				if ( padright )
  2545. 				{
  2546. 					msgline = ( msgline + blankline ).Substring( 0, Console.WindowWidth - 1 );
  2547. 				}
  2548. 				output += msgline + "\n";
  2549. 				input = input.Substring( Math.Min( brpos + 1, input.Length ) ).Trim( );
  2550. 			}
  2551. 			return output.TrimEnd( "\n\r".ToCharArray( ) );
  2552. 		}
  2553.  
  2554. 		public static void WriteHTML( string text )
  2555. 		{
  2556. 			if ( log2html )
  2557. 			{
  2558. 				if ( !htmlopen )
  2559. 				{
  2560. 					OpenHTML( );
  2561. 				}
  2562. 				htmlstream.Write( String.Format( "<span style=\"background-color: {0}; color: {1};\">{2}</span>", Console.BackgroundColor.ToString( ).ToLower( ), Console.ForegroundColor.ToString( ).ToLower( ), text ) );
  2563. 			}
  2564. 		}
  2565.  
  2566. 		public static void WriteLineHTML( string text )
  2567. 		{
  2568. 			if ( log2html )
  2569. 			{
  2570. 				WriteHTML( String.Format( "\n{0}", text ) );
  2571. 			}
  2572. 		}
  2573.  
  2574. 		#endregion Subroutines
  2575.  
  2576.  
  2577. 		#region Error Handling
  2578.  
  2579. 		public static int WriteError( string errorMessage = null )
  2580. 		{
  2581. 			if ( htmlopen )
  2582. 			{
  2583. 				htmlstream.Close( );
  2584. 				File.Delete( htmlfile );
  2585. 			}
  2586.  
  2587. 			if ( !String.IsNullOrEmpty( errorMessage ) )
  2588. 			{
  2589. 				Console.Error.WriteLine( );
  2590. 				Console.ForegroundColor = ConsoleColor.Red;
  2591. 				Console.Error.Write( "ERROR:\t" );
  2592. 				Console.ForegroundColor = ConsoleColor.White;
  2593. 				Console.Error.WriteLine( errorMessage );
  2594. 				Console.ResetColor( );
  2595. 			}
  2596.  
  2597. 			/*
  2598. 			BatCodeCheck,  Version 0.32.1 beta,  Unlicensed
  2599. 			Search batch source code for common errors
  2600.  
  2601. 			Usage:   BATCODECHECK  batchfile [ logging ] [ options ]
  2602.  
  2603. 			or:      BATCODECHECK  [ batchfile ]  /I
  2604.  
  2605. 			Where:   batchfile       is the batch file to be checked and highlighted
  2606. 			Logging: /L[:"logfile"]  Log results in ASCII format, replace file if it exists
  2607. 			         /H[:"htmlfile"] log results in HTML format, replace file if it exists
  2608. 			                         (default output file names equal batchfile name, with
  2609. 			                         extension .log or .html)
  2610. 			Options: /E              include local Environment in defined variables list
  2611. 			         /I              open Interactive settings dialog (licensed version)
  2612. 			         /S              Split one-liners into separate command lines
  2613. 			         /T              add a Timestamp to the default output file names
  2614. 			         /W              Wrap messages in source code (default: chop them)
  2615.  
  2616. 			Notes:   The source code is displayed with highlighted ECHOed text,
  2617. 			         comments, quoted strings, redirection, parenthesis and variables.
  2618. 			         A warning message will be displayed if code errors were found.
  2619. 			         Warning messages in the source code are chopped by default, and
  2620. 			         repeated again (wrapped) at the end of the source code.
  2621. 			         A non-zero return code means something was wrong, either on
  2622. 			         the command line or in the source code. A return code greater
  2623. 			         than 1 indicates the last line where an error was detected.
  2624. 			         If no errors are detected, nothing will be logged.
  2625. 			         False positives cannot be prevented entirely, use common sense
  2626. 			         when evaluating the warnings and error messages.
  2627.  
  2628. 			Written by Rob van der Woude
  2629. 			http://www.robvanderwoude.com
  2630. 			*/
  2631.  
  2632. 			Console.Error.WriteLine( );
  2633. 			Console.Error.WriteLine( "BatCodeCheck,  Version {0}{1}", progver, ( licensed ? String.Empty : ",  (unlicensed)" ) );
  2634. 			Console.Error.WriteLine( "Search batch source code for common errors" );
  2635. 			Console.Error.WriteLine( );
  2636.  
  2637. 			Console.Error.Write( "Or:      " );
  2638. 			Console.ForegroundColor = ConsoleColor.White;
  2639. 			Console.Error.WriteLine( "BATCODECHECK  batchfile [ logging ] [ options ]" );
  2640. 			Console.ResetColor( );
  2641.  
  2642. 			Console.Error.WriteLine( );
  2643.  
  2644. 			Console.Error.Write( "Usage:   " );
  2645. 			Console.ForegroundColor = ConsoleColor.White;
  2646. 			Console.Error.WriteLine( "BATCODECHECK  [ batchfile ]  /I" );
  2647. 			Console.ResetColor( );
  2648.  
  2649. 			Console.Error.WriteLine( );
  2650.  
  2651. 			Console.Error.Write( "Where:   " );
  2652. 			Console.ForegroundColor = ConsoleColor.White;
  2653. 			Console.Error.Write( "batchfile" );
  2654. 			Console.ResetColor( );
  2655. 			Console.Error.Write( "       is the " );
  2656. 			Console.ForegroundColor = ConsoleColor.White;
  2657. 			Console.Error.Write( "batch file" );
  2658. 			Console.ResetColor( );
  2659. 			Console.Error.WriteLine( " to be checked and highlighted" );
  2660.  
  2661. 			Console.Error.Write( "Logging: " );
  2662. 			Console.ForegroundColor = ConsoleColor.White;
  2663. 			Console.Error.Write( "/L[:\"logfile\"]  L" );
  2664. 			Console.ResetColor( );
  2665. 			Console.Error.WriteLine( "og results in ASCII format, replace file if it exists" );
  2666.  
  2667. 			Console.ForegroundColor = ConsoleColor.White;
  2668. 			Console.Error.Write( "         /H[:\"htmlfile\"]" );
  2669. 			Console.ResetColor( );
  2670. 			Console.Error.Write( " log results in " );
  2671. 			Console.ForegroundColor = ConsoleColor.White;
  2672. 			Console.Error.Write( "H" );
  2673. 			Console.ResetColor( );
  2674. 			Console.Error.WriteLine( "TML format, replace file if it exists" );
  2675.  
  2676. 			Console.Error.Write( "                         (" );
  2677. 			Console.ForegroundColor = ConsoleColor.White;
  2678. 			Console.Error.Write( "default" );
  2679. 			Console.ResetColor( );
  2680. 			Console.Error.Write( " output file names equal " );
  2681. 			Console.ForegroundColor = ConsoleColor.White;
  2682. 			Console.Error.Write( "batchfile" );
  2683. 			Console.ResetColor( );
  2684. 			Console.Error.WriteLine( " name, with" );
  2685.  
  2686. 			Console.Error.Write( "                         extension " );
  2687. 			Console.ForegroundColor = ConsoleColor.White;
  2688. 			Console.Error.Write( ".log" );
  2689. 			Console.ResetColor( );
  2690. 			Console.Error.Write( " or " );
  2691. 			Console.ForegroundColor = ConsoleColor.White;
  2692. 			Console.Error.Write( ".html" );
  2693. 			Console.ResetColor( );
  2694. 			Console.Error.WriteLine( ")" );
  2695.  
  2696. 			Console.Error.Write( "Options: " );
  2697. 			Console.ForegroundColor = ConsoleColor.White;
  2698. 			Console.Error.Write( "/E" );
  2699. 			Console.ResetColor( );
  2700. 			Console.Error.Write( "              include local " );
  2701. 			Console.ForegroundColor = ConsoleColor.White;
  2702. 			Console.Error.Write( "E" );
  2703. 			Console.ResetColor( );
  2704. 			Console.Error.WriteLine( "nvironment in defined variables list" );
  2705.  
  2706. 			Console.ForegroundColor = ConsoleColor.White;
  2707. 			Console.Error.Write( "         /I" );
  2708. 			Console.ResetColor( );
  2709. 			Console.Error.Write( "              open " );
  2710. 			Console.ForegroundColor = ConsoleColor.White;
  2711. 			Console.Error.Write( "I" );
  2712. 			Console.ResetColor( );
  2713. 			Console.Error.WriteLine( "nteractive settings dialog (licensed version)" );
  2714.  
  2715. 			Console.ForegroundColor = ConsoleColor.White;
  2716. 			Console.Error.Write( "         /S              S" );
  2717. 			Console.ResetColor( );
  2718. 			Console.Error.WriteLine( "plit one-liners into separate command lines" );
  2719.  
  2720. 			Console.ForegroundColor = ConsoleColor.White;
  2721. 			Console.Error.Write( "         /T" );
  2722. 			Console.ResetColor( );
  2723. 			Console.Error.Write( "              add a " );
  2724. 			Console.ForegroundColor = ConsoleColor.White;
  2725. 			Console.Error.Write( "T" );
  2726. 			Console.ResetColor( );
  2727. 			Console.Error.Write( "imestamp to the " );
  2728. 			Console.ForegroundColor = ConsoleColor.White;
  2729. 			Console.Error.Write( "default" );
  2730. 			Console.ResetColor( );
  2731. 			Console.Error.WriteLine( " output file names" );
  2732.  
  2733. 			Console.ForegroundColor = ConsoleColor.White;
  2734. 			Console.Error.Write( "         /W              W" );
  2735. 			Console.ResetColor( );
  2736. 			Console.Error.WriteLine( "rap messages in source code (default: chop them)" );
  2737.  
  2738. 			Console.Error.WriteLine( );
  2739. 			Console.Error.Write( "Notes:   The source code is displayed with highlighted " );
  2740. 			Console.ForegroundColor = echoedtextfgcolor;
  2741. 			Console.Error.Write( "ECHOed text" );
  2742. 			Console.ResetColor( );
  2743. 			Console.Error.WriteLine( "," );
  2744.  
  2745. 			Console.ForegroundColor = commentfgscolor;
  2746. 			Console.Error.Write( "         comments" );
  2747. 			Console.ResetColor( );
  2748. 			Console.Error.Write( ", " );
  2749. 			Console.ForegroundColor = doublequotedfgcolor;
  2750. 			Console.Error.Write( "quoted strings" );
  2751. 			Console.ResetColor( );
  2752. 			Console.Error.Write( ", " );
  2753. 			Console.ForegroundColor = redirectionfgcolor;
  2754. 			Console.Error.Write( "redirection" );
  2755. 			Console.ResetColor( );
  2756. 			Console.Error.Write( ", " );
  2757. 			Console.ForegroundColor = parenthesisfgcolor;
  2758. 			Console.Error.Write( "parenthesis" );
  2759. 			Console.ResetColor( );
  2760. 			Console.Error.Write( " and " );
  2761. 			Console.ForegroundColor = variablesfgcolor;
  2762. 			Console.Error.Write( "variables" );
  2763. 			Console.ResetColor( );
  2764. 			Console.Error.WriteLine( "." );
  2765.  
  2766. 			Console.Error.WriteLine( "         A warning message will be displayed if code errors were found." );
  2767. 			Console.Error.WriteLine( "         Warning messages in the source code are chopped by default, and" );
  2768. 			Console.Error.WriteLine( "         repeated again (wrapped) at the end of the source code." );
  2769. 			Console.Error.WriteLine( "         A non-zero return code means something was wrong, either on" );
  2770. 			Console.Error.WriteLine( "         the command line or in the source code. A return code greater" );
  2771. 			Console.Error.WriteLine( "         than 1 indicates the last line where an error was detected." );
  2772. 			Console.Error.WriteLine( "         False positives cannot be prevented entirely, use common sense" );
  2773. 			Console.Error.WriteLine( "         when evaluating the warnings and error messages." );
  2774. 			Console.Error.WriteLine( );
  2775. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  2776. 			Console.Error.WriteLine( "http://www.robvanderwoude.com" );
  2777. 			return 1;
  2778. 		}
  2779.  
  2780. 		#endregion Error Handling
  2781. 	}
  2782. }
  2783.  

page last uploaded: 2017-08-21, 14:26