/[AnywhereTS-MSSQL]/trunk/TSAdminTool/frmConfigServices.cs
ViewVC logotype

Annotation of /trunk/TSAdminTool/frmConfigServices.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 46 - (hide annotations) (download)
Thu Jul 12 14:17:14 2012 UTC (8 years, 11 months ago) by william
File size: 27916 byte(s)
+ add logging across ATSAdmin Tool project

1 william 4 using System;
2     using System.Collections.Generic;
3     using System.ComponentModel;
4     using System.Data;
5     using System.Drawing;
6     using System.Text;
7     using System.Windows.Forms;
8     using System.IO;
9     using System.Security.AccessControl;
10     using System.Security.Principal;
11    
12     namespace AnywhereTS
13     {
14     public partial class frmConfigServices : Form
15     {
16     public frmConfigServices()
17     {
18     InitializeComponent();
19     }
20    
21     private atsDataSet.TftpServerDataTable datatableTFTP; // TFTP server directories
22     private atsDataSet.TerminalServerDataTable datatableTerminalServer; // terminal servers
23     const string strATSshareName = @"AnywhereTS$"; // The name of the AnywhereTS Win32 file share for the local TFTP server.
24     const string strShareDesc = @"Used by AnywhereTS";
25     private List<string> tftpUncPaths; // List with the corresponding UNC paths for items in lstTFTP.
26    
27     private void frmConfigureMode_Load(object sender, EventArgs e)
28     {
29     this.Cursor = Cursors.WaitCursor;
30    
31     tftpUncPaths = new List<string>(); // Create a list for the TFTP UNC paths.
32    
33     // Setup database
34     if (ProSupport.tftpServerTableAdapter == null || ProSupport.terminalServerTableAdapter == null) // Is database initiated?
35     {
36     // Databases is not initiated, do it now
37     ProSupport.InitDatabase();
38     }
39    
40     datatableTFTP = new atsDataSet.TftpServerDataTable();
41     ProSupport.tftpServerTableAdapter.Fill(datatableTFTP);
42     datatableTerminalServer = new atsDataSet.TerminalServerDataTable();
43     ProSupport.terminalServerTableAdapter.Fill(datatableTerminalServer);
44    
45     if (ATSGlobals.configured == 0)
46     { // ATS not configured yet
47     RadTSthisComputer.Checked = true;
48     txtTFTPpath.Text = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\AnywhereTS\TFTP";
49     radTFTPinternal.Checked = true;
50     }
51     else
52     { // ATS already configured.
53     txtTFTPpath.Text = ATSGlobals.strTFTPdir;
54     radTFTPinternal.Checked = (ATSGlobals.tftpConfig == 0);
55     radTFTPexternal.Checked = (ATSGlobals.tftpConfig == 1);
56     if (ATSGlobals.terminalServerConfig == 0)
57     { // This computer is terminal server
58    
59     if (datatableTerminalServer.Rows.Count == 0) //Any other terminal servers?
60     { // No other terminal servers
61     RadTSthisComputer.Checked = true;
62     }
63     else
64     { // At least one other
65     radTSboth.Checked = true;
66     }
67     }
68     else
69     { // This computer is not terminal server
70     radTSother.Checked = true;
71     }
72    
73     }
74    
75    
76     // Update terminal server list
77     UpdateTerminalServerList();
78    
79     // Update TFTP server directories list
80     if (ATSGlobals.tftpConfig == 1)
81     {
82     UpdateTftpList();
83     }
84    
85     helpProvider.HelpNamespace = ATSGlobals.strHelpFilePath; // Initiate helpProvider
86    
87     UpdateControls();
88    
89     this.Cursor = Cursors.Default;
90     }
91    
92    
93     private void btnOK_Click(object sender, EventArgs e)
94     {
95     this.Cursor = Cursors.WaitCursor;
96    
97     if (RadTSthisComputer.Checked)
98     ATSGlobals.terminalServerConfig = 0;
99     else if (radTSboth.Checked)
100     ATSGlobals.terminalServerConfig = 0;
101     else if (radTSother.Checked)
102     ATSGlobals.terminalServerConfig = 1;
103     else
104     throw new Exception("Undefined terminal servers config. 42001");
105    
106    
107     string strPath; // The TFTP path
108     strPath = txtTFTPpath.Text.Trim();
109    
110     if (radTFTPinternal.Checked)
111     { // We are using the internal TFTP
112     ATSGlobals.tftpConfig = 0; // Use ATS TFTP
113     // Validate TFTP directory
114     if (strPath.Length == 0)
115     {
116     this.Cursor = Cursors.Default;
117     MessageBox.Show("You must specifiy a directory for the AnywhereTS shared network files.");
118     txtTFTPpath.Focus();
119     return;
120     }
121    
122     if (strPath.StartsWith(".") || strPath.StartsWith(@"\"))
123     {
124     this.Cursor = Cursors.Default;
125     MessageBox.Show("Invalid path for the AnywhereTS shared network files.");
126 william 46 using (log4net.NDC.Push(string.Format("path={0}", strPath)))
127     {
128     Logging.ATSAdminLog.Error("Invalid path for the AnywhereTS shared network files.");
129     }
130 william 4 txtTFTPpath.Focus();
131     return;
132     }
133    
134     // Create TFTP directory
135     try
136     {
137     Directory.CreateDirectory(strPath);
138     }
139     catch (Exception ex)
140     {
141     this.Cursor = Cursors.Default;
142     MessageBox.Show("Could not create directory '" + strPath + "'. Error: " + ex.Message);
143 william 46 using (log4net.NDC.Push(string.Format("SqlException: MESSAGE={0}{1}Diagnostics:{1}{2}", ex.Message, System.Environment.NewLine, ex.ToString())))
144     {
145     using (log4net.NDC.Push(string.Format("directory={0}", strPath)))
146     {
147     Logging.ATSAdminLog.Error("Could not create directory.");
148     }
149     }
150 william 4 txtTFTPpath.Select();
151     return;
152     }
153    
154     // Check that the directory is local
155     ShareUtil shUtil = new ShareUtil(); // Create a util class
156     if (shUtil.IsNetworkPath(strPath))
157     { // The path is a network path
158     MessageBox.Show("A network path cannot be used for the local TFTP server. Please enter a local path.");
159     txtTFTPpath.Select();
160     return;
161     }
162     // Save the TFTP directory to registry
163     ATSGlobals.strTFTPdir = strPath; // Set the C# version of TFTP dir
164     Globals.strTFTPdir = strPath; // Set the VB version of TFTP dir
165     ATSGlobals.SetATSRegValue(ATSGlobals.strRegTFTP_root, ATSGlobals.strTFTPdir);
166    
167     // Delete all TFTP roots from database
168     foreach (DataRow row in datatableTFTP.Rows)
169     {
170     row.Delete();
171     }
172     ProSupport.tftpServerTableAdapter.Update(datatableTFTP);
173    
174     if (!RadTSthisComputer.Checked)
175     { // There are external terminal servers.
176    
177     // Create a share, save the UNC path to the database, add rights to TFTP directory
178     AddTFTPpath(ATSGlobals.strTFTPdir);
179     }
180     else
181     { // We are only using this computer as terminal server
182     SetTFTPrights(ATSGlobals.strTFTPdir);
183     }
184     }
185     else
186     {
187     // Use other TFTP
188     ATSGlobals.tftpConfig = 1; // Use other TFTP
189    
190     if (lstTFTP.Items.Count == 0)
191     {
192     this.Cursor = Cursors.Default;
193     MessageBox.Show("You must choose at least one TFTP server root directory, where AnywhereTS can place client images and configurations.");
194     btnAddTFTPpath.Focus();
195     return;
196     }
197     }
198    
199     // Check if we are using external terminal servers
200     if (RadTSthisComputer.Checked)
201     { // Only this computer is a terminal server. Delete all other terminal servers.
202     foreach (DataRow row in datatableTerminalServer.Rows)
203     {
204     row.Delete();
205     }
206     ProSupport.terminalServerTableAdapter.Update(datatableTerminalServer);
207     }
208     else
209     { // We have external terminal servers
210     if (lstTerminalServer.Items.Count == 0)
211     {
212     this.Cursor = Cursors.Default;
213     MessageBox.Show("You must specify at least one Terminal Server!");
214     btnAddTerminalServer.Focus();
215     return;
216     }
217    
218     // Close database connections
219     datatableTFTP.Dispose();
220     datatableTerminalServer.Dispose();
221     ProSupport.clientTableAdapter.Connection.Close();
222     ProSupport.terminalServerTableAdapter.Connection.Close();
223     ProSupport.clientTableAdapter.Dispose();
224     ProSupport.terminalServerTableAdapter.Dispose();
225    
226     // Configure SQL browser service (needed for the control panel to be able to access the database).
227    
228     bool sqlServerRestarted = DatabaseSupport.EnableNamedPipes(); // Enable named pipes in the SQL server
229     DatabaseSupport.AutostartSQLbrowserService(); // Configure the SQL Browser service to autostart.
230     DatabaseSupport.StartSQLbrowserService(); // Start the SQL Browser service
231    
232     if (sqlServerRestarted)
233     {
234     // SQL server was restarted. Try to reconnect to database in order to repair the database connectivity
235     ProSupport.InitDatabase();
236    
237     try
238     {
239     datatableTFTP = new atsDataSet.TftpServerDataTable();
240     ProSupport.tftpServerTableAdapter.Fill(datatableTFTP); // This operation causes an error and forces the connectivity to recover.
241     }
242     catch
243     { // Do nothing, just catch the error that will most likely occur here.
244     }
245     finally
246     {
247     System.Threading.Thread.Sleep(5000); // Wait for the SQL connection to recover.
248     }
249     }
250     }
251    
252     // Save DHCP options
253     if (radAtsDhcp.Checked)
254     {
255     ATSGlobals.dhcpConfig = 0; // Use the internal DCHP server
256     }
257     else
258     {
259     ATSGlobals.dhcpConfig = 1; // Use the other DCHP server
260     }
261    
262     // Save TFTP configuration to registry
263     ATSGlobals.SetATSRegValue(ATSGlobals.strRegTFTPconfig,ATSGlobals.tftpConfig);
264    
265     // Save DCHP configuration to registry
266     ATSGlobals.SetATSRegValue(ATSGlobals.strRegDHCPconfig, ATSGlobals.dhcpConfig);
267    
268     // Terminate window
269     this.Cursor = Cursors.Default;
270     DialogResult = DialogResult.OK;
271     }
272    
273    
274     private void AddTFTPpath(string strPath)
275     {
276     string uncPath; // The path in UNC format
277     string strLocalPath = ""; // Local path to save in database for display purposes. Only set if path is local
278    
279     ShareUtil shUtil = new ShareUtil(); // Create a util class
280    
281     // Check if path is a network path
282     if (shUtil.IsNetworkPath(strPath))
283     { // The path is a network path
284     if (strPath.StartsWith("\\"))
285     { // The network path is a UNC path, no need to convert it
286     uncPath = strPath;
287     }
288     else
289     { // The network path is not a UNC path, convert it
290     uncPath = UNCconverter.ConvertLocalPathToUnc(strPath);
291     }
292     }
293     else
294     { // User is adding a local path
295     strLocalPath = strPath;
296     string strThisComputer = Environment.MachineName; // The name of this computer
297     // Compile the UNC path
298     uncPath = @"\\" + strThisComputer + @"\" + strATSshareName;
299    
300     // Delete ATS share if it exists
301     shUtil.DeleteShare(strATSshareName);
302    
303     // Delete share from database if it exists
304     ProSupport.tftpServerTableAdapter.Fill(datatableTFTP); // Refresh data table
305     DataRow rowToDelete;
306     rowToDelete = datatableTFTP.Rows.Find(uncPath);
307     if (rowToDelete != null)
308     { // The local TFTP path exists in database, delete it
309     rowToDelete.Delete();
310     ProSupport.tftpServerTableAdapter.Update(datatableTFTP);
311     }
312    
313     // Create network share
314    
315     uint nRetVal;
316     nRetVal = shUtil.CreateShare(strShareDesc, strATSshareName, strPath);
317     if (nRetVal == 0)
318     {
319     // Share created
320     }
321     else
322     {
323     // Error
324     MessageBox.Show("Could not create a share for the AnywereTS TFTP data. Do you have sufficient rights? (25322)");
325     return;
326     }
327    
328     }
329    
330     // Add the remote or local directory path to the database
331     if (uncPath.Length <= 255)
332     { // The path will fit in the database
333     atsDataSet.TftpServerRow row = (atsDataSet.TftpServerRow)datatableTFTP.NewRow();
334     row["Path"] = uncPath;
335     row["LocalPath"] = strLocalPath;
336    
337     try
338     {
339     datatableTFTP.AddTftpServerRow(row);
340     }
341     catch (ConstraintException)
342     {
343     MessageBox.Show("'" + uncPath + "' is already in the list.");
344     return;
345     }
346    
347     ProSupport.tftpServerTableAdapter.Update(datatableTFTP);
348    
349     // Ask if we are going to add righs to the TFTP root directory, set rights.
350     SetTFTPrights(row["Path"].ToString());
351     UpdateTftpList(); // Update the list box
352     lstTFTP.Select();
353     }
354     else
355     { // Path is too long
356     MessageBox.Show("The directory path is too long, please use a path that is max 255 character");
357     }
358    
359     }
360    
361     private void radAtsDhcp_CheckedChanged(object sender, EventArgs e)
362     {
363     UpdateControls(); // Set text "Next >" or "Finish" on the Ok button
364     }
365    
366     private void radExternalDhcp_CheckedChanged(object sender, EventArgs e)
367     {
368     UpdateControls(); // Set text "Next >" or "Finish" on the Ok button
369     }
370    
371     private void UpdateControls()
372     {
373     if (RadTSthisComputer.Checked)
374     { // Only this computer as terminal server
375     lblTerminalServers.Enabled = false;
376     lstTerminalServer.Enabled = false;
377     btnAddTerminalServer.Enabled = false;
378     btnDeleteTerminalServer.Enabled = false;
379     }
380     else
381     { // Other terminal servers
382     lblTerminalServers.Enabled = true;
383     lstTerminalServer.Enabled = true;
384     btnAddTerminalServer.Enabled = true;
385     if (lstTerminalServer.SelectedIndex == -1)
386     btnDeleteTerminalServer.Enabled = false;
387     else
388     btnDeleteTerminalServer.Enabled = true;
389     }
390    
391     if (radTFTPinternal.Checked)
392     {
393     lblTFTPpath.Enabled = true;
394     txtTFTPpath.Enabled = true;
395     btnBrowseTFTP.Enabled = true;
396     lblTFTPdirectories.Enabled = false;
397     lstTFTP.Enabled = false;
398     btnAddTFTPpath.Enabled = false;
399     btnDeleteTFTP.Enabled = false;
400     radAtsDhcp.Enabled = true;
401     lstTFTP.SelectedIndex = -1;
402     btnOK.Text = "Next >";
403     }
404     else
405     {
406     lblTFTPdirectories.Enabled = true;
407     lstTFTP.Enabled = true;
408     btnAddTFTPpath.Enabled = true;
409     if (lstTFTP.SelectedIndex == -1)
410     btnDeleteTFTP.Enabled = false;
411     else
412     btnDeleteTFTP.Enabled = true;
413     lblTFTPpath.Enabled = false;
414     txtTFTPpath.Enabled = false;
415     btnBrowseTFTP.Enabled = false;
416     btnOK.Text = "Finish";
417     radAtsDhcp.Enabled = false;
418     radExternalDhcp.Checked = true;
419     }
420     }
421    
422     private void radTFTPinternal_CheckedChanged(object sender, EventArgs e)
423     {
424     UpdateControls();
425     }
426    
427     private void radTFTPexternal_CheckedChanged(object sender, EventArgs e)
428     {
429     if (radTFTPexternal.Checked && ATSGlobals.tftpConfig == 0)
430     { // We have just de-selected internal TFTP server
431    
432     // Delete all TFTP roots from database
433     foreach (DataRow row in datatableTFTP.Rows)
434     {
435     row.Delete();
436     }
437     ProSupport.tftpServerTableAdapter.Update(datatableTFTP);
438     UpdateTftpList();
439    
440     // Delete the file share for the internal TFTP directory if it exists.
441     ShareUtil shUtil = new ShareUtil(); // Create a util class
442     shUtil.DeleteShare("AnywhereTS$");
443    
444     // ATS is now in a non configured state
445     ATSGlobals.configured = 0;
446     ATSGlobals.SetATSRegValue(ATSGlobals.strRegConfigured, ATSGlobals.configured);
447     }
448     UpdateControls();
449     }
450    
451     // Update the list with TFTP directories from database.
452     private void UpdateTftpList()
453     {
454     lstTFTP.Items.Clear();
455     tftpUncPaths.Clear();
456     ProSupport.tftpServerTableAdapter.Fill(datatableTFTP);
457     foreach (DataRow row in datatableTFTP.Rows)
458     { // Display local path if possible
459    
460    
461     lstTFTP.Items.Add(ProSupport.GetTftpPath(row));
462     //string gg = row["Path"].ToString();
463     //tftpUncPaths.Add(gg);
464     tftpUncPaths.Add(row["Path"].ToString()); // Add the UNC path to the list.
465     }
466     btnDeleteTFTP.Enabled = false;
467     }
468    
469     // Update the list with terminal servers from database.
470     private void UpdateTerminalServerList()
471     {
472     lstTerminalServer.Items.Clear();
473     foreach (DataRow row in datatableTerminalServer.Rows)
474     {
475     lstTerminalServer.Items.Add(row["Path"].ToString());
476     }
477     btnDeleteTerminalServer.Enabled = false;
478     }
479    
480     private void lstTFTP_SelectedIndexChanged(object sender, EventArgs e)
481     {
482     if (lstTFTP.SelectedIndex == -1)
483     { // No item selcted in the TFTP directory list
484     btnDeleteTFTP.Enabled = false;
485     }
486     else
487     { // User has selcted an item in the list
488     btnDeleteTFTP.Enabled = true;
489     }
490     }
491    
492     // Delete the row selected in the TFTP directory list box.
493     private void btnDeleteTFTP_Click(object sender, EventArgs e)
494     {
495     DataRow rowToDelete;
496     rowToDelete = datatableTFTP.Rows.Find(tftpUncPaths[lstTFTP.SelectedIndex]);
497     rowToDelete.Delete();
498     ProSupport.tftpServerTableAdapter.Update(datatableTFTP);
499     UpdateTftpList(); // Update the list box
500    
501     if (datatableTFTP.Rows.Count == 0)
502     { // Last row deleted
503     // ATS is now in a non configured state
504     ATSGlobals.configured = 0;
505     ATSGlobals.SetATSRegValue(ATSGlobals.strRegConfigured, ATSGlobals.configured);
506     }
507     }
508    
509     // Set user rights to TFTP directory, all remote desktop users should be able to write files.
510     void SetTFTPrights(string path)
511     {
512     // Ask if we are going to add righs to the TFTP root directory
513     DialogResult resultRights;
514     resultRights = MessageBox.Show(this, "In order for users to use the AnywhereTS control panel, they need rights to write in the TFTP root directory. Do you want AnywhereTS to add these rights now?", "AnywhereTS", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
515     if (resultRights == DialogResult.Yes)
516     {
517     try
518     {
519     // Create a new DirectoryInfo object.
520     DirectoryInfo dInfo = new DirectoryInfo(path);
521    
522     // Get a DirectorySecurity object that represents the
523     // current security settings.
524     DirectorySecurity dSecurity = dInfo.GetAccessControl();
525    
526     // Add a FileSystemAccessRule to the security settings, to allow write.
527     // No general rights to delete files. These rights are set on the individual files.
528     dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinRemoteDesktopUsersSid, null), FileSystemRights.WriteData, AccessControlType.Allow));
529    
530     // Set the new access settings.
531     dInfo.SetAccessControl(dSecurity);
532    
533     }
534     catch (Exception ex)
535     {
536     this.Cursor = Cursors.Default;
537     MessageBox.Show("Could not set user access rights for TFTP directory. Do you have sufficient right? (29991). Error: " + ex.Message);
538 william 46 using (log4net.NDC.Push(string.Format("SqlException: MESSAGE={0}{1}Diagnostics:{1}{2}", ex.Message, System.Environment.NewLine, ex.ToString())))
539     {
540     Logging.ATSAdminLog.Error("Could not set user access rights for TFTP directory.");
541     }
542 william 4 return;
543     }
544     }
545     }
546    
547     private void btnDeleteTerminalServer_Click(object sender, EventArgs e)
548     {
549     DataRow rowToDelete;
550     rowToDelete = datatableTerminalServer.Rows.Find(lstTerminalServer.SelectedItem);
551     rowToDelete.Delete();
552     ProSupport.terminalServerTableAdapter.Update(datatableTerminalServer);
553     UpdateTerminalServerList(); // Update the list box
554     }
555    
556     private void btnAddTerminalServer_Click(object sender, EventArgs e)
557     {
558     // Show dialog
559     frmAddTerminalServer addTSdialog = new frmAddTerminalServer();
560     DialogResult result = addTSdialog.ShowDialog();
561     if (result == DialogResult.OK)
562     {
563     // Add the server to the database
564     if (addTSdialog.inputstring.Trim().Length <= 255)
565     { // The path will fit in the database
566     atsDataSet.TerminalServerRow row = (atsDataSet.TerminalServerRow)datatableTerminalServer.NewRow();
567     row["Path"] = addTSdialog.inputstring.Trim();
568     try
569     {
570     datatableTerminalServer.AddTerminalServerRow(row);
571     }
572     catch (ConstraintException)
573     {
574     MessageBox.Show("'" + row["Path"] + "' is already in the list.");
575     return;
576     }
577    
578     ProSupport.terminalServerTableAdapter.Update(datatableTerminalServer);
579    
580     UpdateTerminalServerList(); // Update the list box
581     lstTerminalServer.Select();
582     }
583     else
584     { // Path is too long
585     MessageBox.Show("The server path is too long, please use a path that is max 255 character");
586     }
587     }
588     }
589    
590     private void lstTerminalServer_SelectedIndexChanged(object sender, EventArgs e)
591     {
592     if (lstTerminalServer.SelectedIndex == -1)
593     { // No item selcted in the TFTP directory list
594     btnDeleteTerminalServer.Enabled = false;
595     }
596     else
597     { // User has selcted an item in the list
598     btnDeleteTerminalServer.Enabled = true;
599     }
600     }
601    
602     private void RadTSthisComputer_CheckedChanged(object sender, EventArgs e)
603     {
604     UpdateControls();
605     }
606    
607     private void radTSother_CheckedChanged(object sender, EventArgs e)
608     {
609     UpdateControls();
610     }
611    
612     private void radTSboth_CheckedChanged(object sender, EventArgs e)
613     {
614     UpdateControls();
615     }
616    
617     // Add a TFTP server
618     private void btnAddTFTPpath_Click(object sender, EventArgs e)
619     {
620     // Show dialog
621     frmAddTFTP addTFTPdialog = new frmAddTFTP();
622     DialogResult result = addTFTPdialog.ShowDialog();
623     if (result == DialogResult.OK)
624     {
625     string strPath;
626     strPath = addTFTPdialog.inputstring.Trim();
627    
628     if (Directory.Exists(strPath))
629     {
630     // Convert to UNC, share local directory, add directory to database
631     AddTFTPpath(strPath);
632     }
633     else
634     {
635     MessageBox.Show("Error: Cannot find or access the directory. Please check name and rights. (34212)");
636     }
637     UpdateTftpList();
638     }
639     }
640    
641     private void btnBrowseTFTP_Click(object sender, EventArgs e)
642     {
643     // Show the FolderBrowserDialog.
644     Rebrowse:
645     DialogResult result = folderBrowserDialog.ShowDialog();
646     if (result == DialogResult.OK)
647     {
648 william 46 string strPath=""; // The path provided by the user
649 william 4
650     // Browse to a folder by displaying the folder browse Dialog
651     try
652     {
653     strPath = folderBrowserDialog.SelectedPath.Trim();
654     }
655     catch (NotSupportedException)
656     { // User has browsed to a non folder
657     MessageBox.Show("Object is not a file folder.");
658 william 46 using (log4net.NDC.Push(string.Format("path={0}", strPath)))
659     {
660     Logging.ATSAdminLog.Error("Object is not a file folder.");
661     }
662 william 4 goto Rebrowse;
663     }
664     txtTFTPpath.Text = strPath;
665     txtTFTPpath.Select();
666     }
667     }
668     }
669     }
670    

  ViewVC Help
Powered by ViewVC 1.1.22