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