@@ -46,18 +46,15 @@ private void ConfirmView(object obj)
46
46
// Parse the XAML content to create a UserControl object
47
47
UserControl View = ( UserControl ) XamlReader . Parse ( xamlContent ) ;
48
48
49
- // Find the SecOpsDataGrid
49
+ // Finding elements
50
50
DataGrid SecOpsDataGrid = ( DataGrid ) View . FindName ( "SecOpsDataGrid" ) ;
51
-
52
51
TextBlock TotalCurrentlyDisplayedSecOpsTextBlock = ( TextBlock ) View . FindName ( "TotalCurrentlyDisplayedSecOps" ) ;
53
-
54
- #region ToggleButtons
55
52
ToggleButton CompliantItemsToggleButton = ( ToggleButton ) View . FindName ( "CompliantItemsToggleButton" ) ;
56
53
ToggleButton NonCompliantItemsToggleButton = ( ToggleButton ) View . FindName ( "NonCompliantItemsToggleButton" ) ;
57
-
58
- CompliantItemsToggleButton . IsChecked = true ;
59
- NonCompliantItemsToggleButton . IsChecked = true ;
60
- #endregion
54
+ Button ExportResultsButton = ( Button ) View . FindName ( "ExportResultsButton" ) ;
55
+ TextBox textBoxFilter = ( TextBox ) View . FindName ( "textBoxFilter" ) ;
56
+ TextBlock TotalCountTextBlock = ( TextBlock ) View . FindName ( "TotalCountTextBlock" ) ;
57
+ ComboBox ComplianceCategoriesSelectionComboBox = ( ComboBox ) View . FindName ( "ComplianceCategoriesSelectionComboBox" ) ;
61
58
62
59
// Initialize an empty security options collection
63
60
ObservableCollection < SecOp > SecOpsObservableCollection = [ ] ;
@@ -73,7 +70,7 @@ private void ConfirmView(object obj)
73
70
void UpdateCurrentVisibleItemsTextBlock ( )
74
71
{
75
72
// Get the count of all of the current items in the CollectionView
76
- int totalDisplayedItemsCount = SecOpsCollectionView ! . OfType < SecOp > ( ) . Count ( ) ;
73
+ int totalDisplayedItemsCount = SecOpsCollectionView . OfType < SecOp > ( ) . Count ( ) ;
77
74
78
75
// Display the count in a text box in the GUI
79
76
TotalCurrentlyDisplayedSecOpsTextBlock . Text = $ "Showing { totalDisplayedItemsCount } Items";
@@ -83,7 +80,7 @@ void UpdateCurrentVisibleItemsTextBlock()
83
80
void ApplyFilters ( string filterText , bool includeCompliant , bool includeNonCompliant )
84
81
{
85
82
// Apply a filter to the collection view based on the filter text and toggle buttons
86
- SecOpsCollectionView ! . Filter = memberObj =>
83
+ SecOpsCollectionView . Filter = memberObj =>
87
84
{
88
85
if ( memberObj is SecOp member )
89
86
{
@@ -109,9 +106,6 @@ void ApplyFilters(string filterText, bool includeCompliant, bool includeNonCompl
109
106
UpdateCurrentVisibleItemsTextBlock ( ) ;
110
107
}
111
108
112
- // Finding the textboxFilter element
113
- TextBox textBoxFilter = ( TextBox ) View . FindName ( "textBoxFilter" ) ;
114
-
115
109
#region event handlers for data filtration
116
110
// Attach event handlers to the text box filter and toggle buttons
117
111
textBoxFilter . TextChanged += ( sender , e ) => ApplyFilters ( textBoxFilter . Text , CompliantItemsToggleButton . IsChecked ?? false , NonCompliantItemsToggleButton . IsChecked ?? false ) ;
@@ -149,101 +143,83 @@ void ApplyFilters(string filterText, bool includeCompliant, bool includeNonCompl
149
143
150
144
#endregion
151
145
152
-
153
146
#region ComboBox
154
- // Finding the ComplianceCategoriesSelectionComboBox ComboBox
155
- ComboBox ComplianceCategoriesSelectionComboBox = ( ComboBox ) View . FindName ( "ComplianceCategoriesSelectionComboBox" ) ;
156
147
157
148
// Get the valid compliance category names
158
149
List < string > catsList = [ .. Enum . GetNames < ComplianceCategories > ( ) ] ;
159
150
160
- // Add an empty item to the list at the beginning
161
- catsList . Insert ( 0 , "" ) ;
151
+ // Add an item to the list at the beginning to be the default value
152
+ catsList . Insert ( 0 , "All Categories " ) ;
162
153
163
154
// Set the ComboBox's ItemsSource to the updated list
164
155
ComplianceCategoriesSelectionComboBox . ItemsSource = catsList ;
165
156
166
157
#endregion
167
158
168
- // Register the RefreshButton as an element that will be enabled/disabled based on current activity
159
+ // Register the elements that will be enabled/disabled based on current global activity
169
160
ActivityTracker . RegisterUIElement ( RefreshButton ) ;
161
+ ActivityTracker . RegisterUIElement ( ComplianceCategoriesSelectionComboBox ) ;
170
162
171
163
// Set up the Click event handler for the Refresh button
172
164
RefreshButton . Click += async ( sender , e ) =>
173
165
{
174
-
175
166
// Only continue if there is no activity other places
176
- if ( ! ActivityTracker . IsActive )
167
+ if ( ActivityTracker . IsActive )
177
168
{
178
- // mark as activity started
179
- ActivityTracker . IsActive = true ;
180
-
181
- // Clear the current security options before starting data generation
182
- SecOpsObservableCollection . Clear ( ) ;
183
- SecOpsCollectionView . Refresh ( ) ; // Refresh the collection view to clear the DataGrid
169
+ return ;
170
+ }
184
171
185
- // Disable the Refresh button while processing
186
- // Set text blocks to empty while new data is being generated
187
- Application . Current . Dispatcher . Invoke ( ( ) =>
188
- {
189
- TextBlock TotalCountTextBlock = ( TextBlock ) View . FindName ( "TotalCountTextBlock" ) ;
172
+ // mark as activity started
173
+ ActivityTracker . IsActive = true ;
190
174
191
- if ( TotalCountTextBlock is not null )
192
- {
193
- // Update the text of the TextBlock to show the total count
194
- TotalCountTextBlock . Text = "Loading..." ;
195
- }
175
+ // Clear the current security options before starting data generation
176
+ SecOpsObservableCollection . Clear ( ) ;
177
+ SecOpsCollectionView . Refresh ( ) ; // Refresh the collection view to clear the DataGrid
196
178
197
- UpdateCurrentVisibleItemsTextBlock ( ) ;
198
- } ) ;
179
+ // Set text blocks to empty while new data is being generated
180
+ Application . Current . Dispatcher . Invoke ( ( ) =>
181
+ {
182
+ TotalCountTextBlock . Text = "Loading..." ;
183
+ CompliantItemsToggleButton . Content = "Compliant Items" ;
184
+ NonCompliantItemsToggleButton . Content = "Non-Compliant Items" ;
199
185
200
- // Run the method asynchronously in a different thread
201
- await Task . Run ( ( ) =>
202
- {
203
- // Get fresh data for compliance checking
204
- Initializer . Initialize ( null , true ) ;
186
+ UpdateCurrentVisibleItemsTextBlock ( ) ;
187
+ } ) ;
205
188
206
- // initialize the variable to null
207
- string ? SelectedCategory = null ;
189
+ // Run the method asynchronously in a different thread
190
+ await Task . Run ( ( ) =>
191
+ {
192
+ // Get fresh data for compliance checking
193
+ Initializer . Initialize ( null , true ) ;
208
194
209
- // Use the App dispatcher since this is being done in a different thread
210
- app . Dispatcher . Invoke ( ( ) =>
211
- {
212
- if ( ComplianceCategoriesSelectionComboBox . SelectedItem is not null )
213
- {
214
- // Get the currently selected value in the Compliance Checking category ComboBox if it exists
215
- var SelectedComplianceCategories = ComplianceCategoriesSelectionComboBox . SelectedItem ;
216
-
217
- // Get the currently selected compliance category
218
- SelectedCategory = SelectedComplianceCategories ? . ToString ( ) ;
219
- }
220
- } ) ;
221
-
222
- // if user selected a category for compliance checking
223
- if ( ! string . IsNullOrEmpty ( SelectedCategory ) )
224
- {
225
- // Perform the compliance check using the selected compliance category
226
- InvokeConfirmation . Invoke ( [ SelectedCategory ] ) ;
227
- }
228
- else
229
- {
230
- // Perform the compliance check for all categories
231
- InvokeConfirmation . Invoke ( null ) ;
232
- }
233
- } ) ;
195
+ // Get the currently selected compliance category - Use the App dispatcher since this is being done in a different thread
196
+ string SelectedCategory = app . Dispatcher . Invoke ( ( ) => ComplianceCategoriesSelectionComboBox . SelectedItem . ToString ( ) ! ) ;
234
197
235
- // After InvokeConfirmation is completed, update the security options collection
236
- await Application . Current . Dispatcher . InvokeAsync ( ( ) =>
198
+ // If all categories is selected which is the default
199
+ if ( string . Equals ( SelectedCategory , "All Categories" , StringComparison . OrdinalIgnoreCase ) )
200
+ {
201
+ // Perform the compliance check for all categories
202
+ InvokeConfirmation . Invoke ( null ) ;
203
+ }
204
+ // if user selected a category for compliance checking
205
+ else
237
206
{
238
- LoadMembers ( ) ; // Load updated security options
239
- RefreshButton . IsChecked = false ; // Uncheck the Refresh button
207
+ // Perform the compliance check using the selected compliance category
208
+ InvokeConfirmation . Invoke ( [ SelectedCategory ] ) ;
209
+ }
210
+ } ) ;
240
211
241
- UpdateCurrentVisibleItemsTextBlock ( ) ;
242
- } ) ;
212
+ // After InvokeConfirmation is completed, update the security options collection
213
+ await Application . Current . Dispatcher . InvokeAsync ( ( ) =>
214
+ {
215
+ LoadMembers ( ) ; // Load updated security options
216
+ RefreshButton . IsChecked = false ; // Uncheck the Refresh button
243
217
244
- // mark as activity completed
245
- ActivityTracker . IsActive = false ;
246
- }
218
+ UpdateCurrentVisibleItemsTextBlock ( ) ;
219
+ } ) ;
220
+
221
+ // mark as activity completed
222
+ ActivityTracker . IsActive = false ;
247
223
} ;
248
224
249
225
/// <summary>
@@ -289,17 +265,11 @@ void LoadMembers()
289
265
/// <param name="ShowNotification">If set to true, this method will display end of confirmation toast notification</param>
290
266
void UpdateTotalCount ( bool ShowNotification )
291
267
{
292
-
293
268
// calculates the total number of all security options across all lists, so all the items in each category that exist in the values of the main dictionary object
294
269
int totalCount = GlobalVars . FinalMegaObject . Values . Sum ( list => list . Count ) ;
295
270
296
- // Find the TextBlock used to display the total count
297
- TextBlock TotalCountTextBlock = ( TextBlock ) View . FindName ( "TotalCountTextBlock" ) ;
298
- if ( TotalCountTextBlock is not null )
299
- {
300
- // Update the text of the TextBlock to show the total count
301
- TotalCountTextBlock . Text = $ "{ totalCount } Total Verifiable Security Checks";
302
- }
271
+ // Update the text of the TextBlock to show the total count
272
+ TotalCountTextBlock . Text = $ "{ totalCount } Total Verifiable Security Checks";
303
273
304
274
// Get the count of the compliant items
305
275
string CompliantItemsCount = SecOpsCollectionView . SourceCollection
@@ -324,6 +294,89 @@ void UpdateTotalCount(bool ShowNotification)
324
294
}
325
295
}
326
296
297
+ // Event handler for the Export Results button
298
+ ExportResultsButton . Click += async ( sender , e ) =>
299
+ {
300
+ try
301
+ {
302
+ ExportResultsButton . IsEnabled = false ;
303
+
304
+ await Task . Run ( ( ) =>
305
+ {
306
+ // Show the save file dialog to let the user pick the save location
307
+ Microsoft . Win32 . SaveFileDialog saveFileDialog = new ( )
308
+ {
309
+ FileName = $ "Harden Windows Security Compliance Check Results at { DateTime . Now : yyyy-MM-dd HH-mm-ss} ", // Default file name
310
+ DefaultExt = ".csv" , // Default file extension
311
+ Filter = "CSV File (.csv)|*.csv" // Filter files by extension
312
+ } ;
313
+
314
+ // Show the dialog and check if the user picked a file
315
+ bool ? result = saveFileDialog . ShowDialog ( ) ;
316
+
317
+ if ( result == true )
318
+ {
319
+ // Get the selected file path from the dialog
320
+ string filePath = saveFileDialog . FileName ;
321
+
322
+ try
323
+ {
324
+ ExportSecOpsToCsv ( filePath , SecOpsObservableCollection ) ;
325
+ }
326
+ catch ( Exception ex )
327
+ {
328
+ Logger . LogMessage ( $ "Failed to export the results to the file: { ex . Message } ", LogTypeIntel . ErrorInteractionRequired ) ;
329
+ }
330
+
331
+ Logger . LogMessage ( $ "Compliance check results have been successfully exported.", LogTypeIntel . InformationInteractionRequired ) ;
332
+ }
333
+ } ) ;
334
+ }
335
+ finally
336
+ {
337
+ ExportResultsButton . IsEnabled = true ;
338
+ }
339
+ } ;
340
+
341
+
342
+ // To Export the results of the compliance checking to a file
343
+ void ExportSecOpsToCsv ( string filePath , ObservableCollection < SecOp > secOps )
344
+ {
345
+ // Defining the header row
346
+ string [ ] headers = [ "FriendlyName" , "Compliant" , "Value" , "Name" , "Category" , "Method" ] ;
347
+
348
+ // Open the file for writing
349
+ using StreamWriter writer = new ( filePath ) ;
350
+
351
+ // Write the header row
352
+ writer . WriteLine ( string . Join ( "," , headers . Select ( header => $ "\" { header } \" ") ) ) ;
353
+
354
+ // Write each SecOp object as a row in the CSV
355
+ foreach ( SecOp secOp in secOps )
356
+ {
357
+ string [ ] row = [
358
+ EscapeForCsv ( secOp . FriendlyName ) ,
359
+ EscapeForCsv ( secOp . Compliant . ToString ( CultureInfo . InvariantCulture ) ) ,
360
+ EscapeForCsv ( secOp . Value ) ,
361
+ EscapeForCsv ( secOp . Name ) ,
362
+ EscapeForCsv ( secOp . Category . ToString ( ) ) ,
363
+ EscapeForCsv ( secOp . Method )
364
+ ] ;
365
+
366
+ writer . WriteLine ( string . Join ( "," , row ) ) ;
367
+ }
368
+ }
369
+
370
+ // Local method to enclose values in double quotes
371
+ string EscapeForCsv ( string ? value )
372
+ {
373
+ // If the value is null, empty or whitespace, return an empty string
374
+ if ( string . IsNullOrWhiteSpace ( value ) ) return string . Empty ;
375
+
376
+ // Otherwise, escape double quotes and wrap the value in double quotes
377
+ return $ "\" { value . Replace ( "\" " , "\" \" " , StringComparison . OrdinalIgnoreCase ) } \" ";
378
+ }
379
+
327
380
// Cache the Confirm view for future use
328
381
_viewCache [ "ConfirmView" ] = View ;
329
382
0 commit comments