ממשק משתמש לחיפוש שינויים שבוצעו על חשבונות Active-Directory

ממשק משתמש לחיפוש שינויים שבוצעו על חשבונות Active-Directory

זהו החלק השני של כלי לאיתור שינויים שבוצעו בחשבונות ב-Active-Directory.

החלק הראשון

תקציר:

בחלק הראשון נבנה מנגנון אוטומטי שאוסף לוגים על שינויים בחשבונות משתמשים, מעבד אותם ולוקח מתוכם מידע רלוונטי, ומכניס את הנתונים המעובדים לתוך טבלאות חודשיות של CSV.

לפני שבאתי לחלק הנוכחי, החלטתי לכתוב מדריך בסיסי ליצירת ממשק משתמש ב-PowerShell. גם כדי לתת לקורא בסיס להבנה כיצד ממשק משתמש יעבוד, וגם כדי לתת לכל מי שרוצה את הכלים להתחיל לבנות ממשקי משתמש משלו.

ולבסוף הגענו לחלק זה, שבו יוסבר כיצד לבנות ממשק משתמש שבאמצעותו ניתן בקלות לאתר אירועים ושינויים ספציפיים בחשבונות משתמשים, כאלה שנשמרו בקבצי ה-CSV שנוצרו באמצעות התהליך מהחלק הראשון.

מעט על השימוש בכלי:

הכלי פשוט ונוח לשימוש, ונועד בעיקר לשימושם של אנשי מחלקות IT שרוצים לעקוב אחר שינויים בחשבונות משתמשים – כמו HelpDesk, System ואנשי אבטחת מידע.

בסוף המאמר יופיעו שלושה נספחים. האחד מכיל את הקוד שמפעיל את הכלי. השני מכיל את התוכן שאמור לקפוץ בלחיצה על כפתור Help, והשלישי מכיל הוראות כיצד להפוך את הסקריפט של PowerShell לתוכנה הנפתחת באמצעות אייקון על שולחן העבודה (או בכל מיקום לבחירת המשתמש).

קצת ארוך…

המאמר מסביר באריכות רבה את פעולת הקוד ואת הסיבות לשימוש בדרכים מסוימות על מנת לבצע את הפעולות. כל זה כדי ללמד את מי שרוצה דרכים שונות לכתוב PowerShell Script.

לכן המאמר יכול להיות ארוך ומתעמק ואפילו טרחני, למי שההסברים על דרך הפעולה וכתיבת הקוד לא מעניינים אותו.

מי שרק רוצה להשתמש בכלי, יכול לקרוא את החלק של השימוש בממשק, או אפילו לדלג ישר לנספחים ולהוציא משם את הקוד שמריץ את הכלי, את התוכן לקובץ עזרה, ואת ההוראות כיצד להפעיל את הכלי באמצעות אייקון על שולחן העבודה.

השימוש בממשק:

ממשק חיפוש שינויים בחשבונות משתמשים

כפי שניתן לראות בתמונה, הממשק מחולק לארבע קבוצות, כאשר שלוש מתוכן מופרדות באמצעות שני קווים. העיצוב פשוט, מינימלי ותכליתי.

פריסה:

למעלה יש תיבת כתיבה שבה אפשר להזין את שם החשבון שבו בוצעו השינויים. אם לא מזינים שם משתמש, החיפוש ימצא את כל החשבונות שמתאימים למאפיינים האחרים שהוגדרו.

מתחת לקו ישנה קבוצה של סוגי שינויים שניתן לבצע על חשבונות. אם יודעים איזה שינוי בוצע ורוצים למקד את החיפוש, ניתן לסמן את הסוג הרצוי. אם לא מסמנים אף סוג, החיפוש ימצא את כל הסוגים.

מימין יש את אזור התאריך.

בניגוד לשתי הקבוצות הקודמות, אזור התאריך הוא חובה, והמילוי שלו חייב להתבצע בדיוק בפורמט הרשום.

התהליך לא ירוץ ללא מילוי נכון של שדות התאריך.

לחצנים:

לבסוף מגיעים לקבוצה האחרונה – קבוצת הלחצנים. שם אפשר ללחוץ על Find כדי למצוא אירועים בטווח הזמן שהוגדר, לסגור את החלון, או להקפיץ חלון שמסביר את פעולת הממשק ועוזר למשתמש לדעת כיצד להזין את נתוני החיפוש ואיך לקרוא את הנתונים.

בהנחה ששדות התאריך הוזנו כהלכה, לחיצה על Find תעלה חלון תוצאות דומה לזה שרואים בתמונה.

תוצאות חיפוש

מבנה חלון התוצאות:

כפי שניתן לראות, עבור כל אירוע ישנה שורה בחלון. השורה מכילה את השדות הבאים לפי הסדר משמאל לימין:

1.     ActBy – מי ביצע את השינויים בחשבון.

2.     Target – איזה חשבון השתנה.

3.     Result – האם הפעולה בוצעה בהצלחה או שזה היה נסיון שנכשל.

4.     Action – תיאור במילה או שתים של הפעולה שהתבצעה בחשבון.

5.     TimeCreated – חותמת זמן מרגע השינוי.

6.     Event – מה שנקרא EventID. המספר המסמל את סוג האירוע שהתבצע מבחינת המערכת.

7.     Message – לפעמים לא נרצה להסתפק בנתונים המופיעים בטבלה, ונרצה לקרוא את כל הלוג של האירוע. זה יכול לקרות בעיקר בהודעות על חשבון שהשתנה. במקרה כזה נרצה לדעת איזה שדה השתנה בחשבון, והפירוט הזה מופיע רק בהודעה המלאה.

עוד קצת על Message:

Message מופיע בתור לחצן. לחיצה עליו מקפיצה חלונית נוספת שבה מופיעה ההודעה המלאה של האירוע.

התיעוד המלא של השינוי

גודל החלון וגלילה:

גובה חלון התוצאות יכול להשתנות בהתאם למספר השורות – עד 20 שורות. מ-20 שורות ומעלה, החלון ישאר בגובה המכיל כ-20 שורות, וניתן יהיה לגלול למטה ולמעלה כדי להגיע לשורות נוספות.

לחצן Close מופיע בתחתית חלון התוצאות. אם מספר השורות גבוה, צריך לגלול עד למטה כדי למצוא את לחצן הסגירה.

לאחר שהוסברה פעולת הכלי על חלקיו השונים, אפשר להתקדם לקוד שמאחוריו ולהסביר את צורת הבניה ואת הלוגיקה של הקוד.

הקוד:

–         כרגיל, תיאור הקוד ומספרי השורות מתנהל ביחס לנראות שלו בעורך הקוד של PowerShell – PowerShell ISE.

הקוד בכללותו נראה קצת ארוך יחסית לסקריפט, וכולל כ-400 שורות. אבל אין צורך להבהל מאורך הקוד. הרבה מן הנפח כולל שורות תיעוד, וחלק נכבד מאוד כולל הגדרה של מאפייני החלון וממשק המשתמש. שזו חזרה פשוטה על אותם המאפיינים בשינויים קלים.

מתוך כל האורך הזה, יש בערך 70-100 שורות של תהליך לוגי שרץ, כשחלק ממנו זה תיעוד, ובלוק נוסף סוגר מאפייני מראה של חלון התוצאות.

בקיצור, אין צורך להבהל וכדאי לצלול פנימה וללמוד במה מדובר.

הקוד מחולק למקטעים שונים המבצעים פעולות שונות.

יבוא והגדרה ראשוניים (שורות 1-9):

בתחילת הקוד מתבצע יבוא של ספריות הדרושות לבניית ממשק המשתמש. לאחר מכן מוגדר משתנה המכיל אובייקט שיכול להקפיץ חלונות הודעה לפי דרישה. האובייקט הזה יבוא לידי שימוש בכמה הזדמנויות לאורך הקוד, על מנת להקפיץ הודעות שונות למשתמש.

לבסוף מוגדר הנתיב שבו התהליך מחפש את הקבצים. מדובר בקבצי הנתונים המכילים תיעוד של השינויים בחשבונות השונים, וגם קובץ טקסט המכיל מידע למשתמש ועזרה כיצד להשתמש בממשק. הקובץ נפתח בלחיצה על לחצן Help.

בסוף המאמר יופיע נספח ובו התוכן של אותו הקובץ. צריך רק להעתיק אותו לקובץ טקסט בשם Help.txt ולהניח בנתיב שבו נשמרים קבצי ה-CSV המכילים את המידע.

את אותו נתיב מכניסים לתוך משתנה בשורה 9.

יצירת חלון תוצאות (שורות 14-21):

במקטע זה מוגדרת פונקציה. הפונקציה יוצרת חלון פשוט. בהמשך יתווספו מאפיינים ונתונים אל תוך החלון, בהתאם לנסיבות.

יש לשים לב שהחלון הוגדר להפתח במרכז הצג, ולאפשר גלילה במידה והתוכן יחרוג מגבולות החלון שהוגדרו לו.

אם כן, יש גם לשים לב שגובה החלון שהוגדר קטן מאוד. זה חשוב להמשך התהליך.

הוספת שורות ומאפיינים לחלון התוצאות (שורות 26-111):

כאן מוגדרת פונקציה נוספת. הפונקציה הזו מופעלת ורצה בכל פעם שצריך להוסיף שורה לחלון התוצאות. כל שורה מכילה נתונים על אירוע אחד, וכולן מסודרות בתצורת טבלה.

פרמטרים (שורות 30-39)

הפונקציה עובדת עם שורה של פרמטרים שאת ערכיהם היא מקבלת מהתהליך הראשי שקורא לה, אך לרוב הפרמטרים מוגדרים ערכי ברירת מחדל. זאת משום שהשורה הראשונה צריכה להיות שורת הכותרת של הטבלה, ובה מופיעים נתונים קבועים. בכל שאר השורות שתתווספנה, הנתונים ישתנו בהתאם לאירוע שאותו הם מתארים.

שורה 40

כאן

מוגדרת פונקציה קצרה, או בעצם ScriptBlock – שהוא קטע קוד תחום בסוגריים מסולסלים.

כאן הקטע קצר במיוחד, וכל מה שהוא עושה זה להקפיץ חלונית הודעה (מהאובייקט שהוגדר בתחילת הקוד, שורה 7) המכילה את ערכו של המשתנה $Whole. המשתנה הזה מהווה פרמטר לפונקציה של הוספת שורה, ומכיל את ההודעה השלמה של הלוג הנוכחי.

לסיכום – ה-ScriptBlock הזה מקפיץ חלונית הודעה המכילה את לוג הנוכחי בשלמותו. ואת כל הפעולה הזו מכניסים לתוך המשתנה $do.

כפי שהוסבר במדריך ליצירת GUI, ניתן להכניס קטע קוד לתוך משתנה, ואז להגדיר לחיצה על כפתור שתקרא למשתנה ותפעיל את הקוד ההוא.

בהמשך נגיע ללחיצה על הכפתור.

שורה 47

כאן מוגדרת נקודת המיקום הרוחבית שבה מתחילים להופיע הפריטים בחלון התוצאות – 20 נקודות משמאל.

שורה 48

כאן מופיעה נקודת המיקום האנכית שבה מתחילה כל שורה – כמה נקודות מראש החלון סופרים, כדי להגיע לתחילת השורה.

ההיגיון שעומד מאחורי הצורה שבה כתובה השורה הזו, מגדיר 10 נקודות מרווח מראש החלון לשורה הראשונה, ואז 5 נקודות מרווח בין שורה לשורה. כי נקודות ההתחלה של כל שורה מגיעות במכפלות של 25 נקודות, אבל הגובה של כל שורה עומד על 20 נקודות. מה שמשאיר 5 נקודות מרווח עד לנקודה שבה מתחילה השורה הבאה.

שורות 51-91

כאן מוגדרים 6 מתוך 7 הנתונים המופיעים בכל שורה.

כפי שניתן לשים לב, צורת ההגדרה די חוזרת על עצמה, ויש שינויים קטנים בנתונים שנוגעים לרוחב שהעמודה צריכה לתפוס.

חשוב לשים לב שבמקום מיקומים מוחלטים עבור משבצת, קבוע מיקום יחסי עבור כל פריט. הגובה שבו מתחילה כל שורה, איננו מספר נקודות קבוע מן המסגרת, אלא מספר שמשתנה בהתאם למספר השורה. כך גם המיקום לרוחב החלון לא ניתן במספרים מוחלטים (למרות שהיה אפשר לעשות את זה במקרה זה).

המשבצת הראשונה מתחילה על פי המשתנה $l, שבעצם אינו משתנה. ערכו תמיד עומד על 20, ובפועל מצביע על 20 נקודות מגבולו השמאלי של החלון.

אבל כל משבצת שאחריה, מתחילה בחישוב על פי המיקום ביחס למשבצת שלפניה.

וזה כתוב כך:

תחילת המיקום יהיה – לוקחים את נקודת ההתחלה של המשבצת הקודמת, מוסיפים לה את רוחב המשבצת הקודמת, מוסיפים עוד 10 נקודות כדי לתת רווח בין העמודות – והנה נקודת ההתחלה של המשבצת.

כך נקבע בצורה יחסית הערך של כל עמודה ביחס לזו שלפניה.

היה אפשר לחשב מראש מה הרוחב של כל עמודה ולקבוע את המיקומים במספרים קבועים, ולא לתת לקוד לחשב את זה בכל פעם שהוא רץ.

אבל זה נכתב כדי להראות את האפשרות הזו, שלפעמים היא הכרחית. לדוגמה – בחישובי מיקום הגובה של השורות בחלון התוצאות.

לחצן הודעה ומתודת GetNewClosure():

לאחר שהוגדרו שש העמודות הראשונות, בשורות 94-107 מוגדר הפריט השביעי והאחרון – Message.

בעוד שכל הפריטים האחרים תמיד מורכבים מטקסט – ולכן בשורת הכותרת הם מקבלים ערך ברירת-מחדל, ובכל שאר השורות הטקסט שלהם מגיע מתוך משתנה, הפריט האחרון הוא לחצן.

יצירת העמודה הזו מאתגרת יותר מאשר העמודות האחרות.

תחילה היה צריך להציב תנאי (שורות 94-101) שמבדיל בין שורת הכותרת לשורות האחרות. כי בשורת הכותרת יש טקסט לכותרת, ובכל השורות שמכילות נתונים, יש לחצן.

לאחר שהוגדר הלחצן, הוגדרה הפעולה שהלחצן יבצע – הפעולה שנכנסה למשתנה $do שהוגדר והוסבר לעיל.

וכאן אני רוצה להתעכב ולהסביר את היחודיות שדרשה פעולה זו.

שמירת משתנה שכל הזמן משתנה:

כל אחד מהערכים שבעמודות השונות שבכל שורה, נקבע באותו הרגע שבה מוגדרים הפריטים בשורה. גם הערך שבעמודה האחרונה נקבע באותו הרגע – נקבע כלחצן המכיל את התווית Message. אבל הפעולה שאותה מבצע הלחצן, היא הבעיה. כי הלחצן קורא למשתנה. והבעיה עם משתנה היא… שהוא משתנה.

בגלל שכל השורות נוצרות באמצעות פונקציה, הערכים שיוצרים כל שורה, שונים בכל פעם. ואם יקבע הקוד שהלחצן Message יקרא למשתנה $whole, זה מה שיקרה כשילחצו עליו.

אבל כשילחצו על הכפתור, הפונקציה סיימה לרוץ. והמשתנה שהיה קיים בסביבת הפונקציה, אינו זמין. בעיה.

ואם נקבע שהמשתנה יהיה גלובלי, עדיין הוא משתנה בכל פעם שהפונקציה רצה. מאז שהוגדר הלחצן, הפונקציה הספיקה לרוץ עוד כמה פעמים, וכעת יש ערך אחר למשתנה $whole – הערך מהפעם האחרונה שבה רצה הפונקציה.

ככה שאם יש לנו 5 שורות ובסופה של כל שורה לחצן Message, מה שיקרה זה שכל הלחצנים יפתחו את אותה ההודעה. כי כולם קוראים לאותו המשתנה.

הפתרון:

כאן באה לידי שימוש המתודה GetNewClosure() שחלה על אותו ScriptBlock שנכנס למשתנה $do.

כאשר כותבים ScriptBlock ובתוכו משתנים, בכל פעם שירוץ ה-ScriptBlock, הוא יחפש את הערך הנוכחי של אותם משתנים. אם רוצים לנעול את הערך היוצא של אותם משתנים, או את התוצאה של אותו ScriptBlock, משתמשים במתודה GetNewClosure.

המתודה הזו בעצם מייצאת את התוצאה של אותו ScriptBlock ומנתקת אותה מכל קשר למשתנים ולבלוק שהפיק אותה. התוצאה הזו נושאת ערך עצמאי שניתן להציב בתוך משתנה, וישמר ביציבות הלאה, גם אם הסביבה שיצרה אותו כבר החליפה את ערכה והקשרה כמה פעמים.

אחרת:

אילו הוצב לעיל ScriptBlock אל תוך המשתנה $do, המשתנה היה מריץ את ה-ScriptBlock, שהיה קורא למשתנה $whole ומקפיץ את ערכו בתוך חלונית הודעה.

אבל כעת, לא הוצב ה-ScriptBlock בתוך המשתנה. עם הפעלת המתודה, ה-ScriptBlock רץ אז, וכל ההקשר שלו נסגר ונחתם. התוצאה שלו – חלונית הודעה הנושאת את הלוג של אותו האירוע מאותה ריצה של הפונקציה, נשמר בתוך המשתנה $do, ונקבע כתוצאה של הלחצן Message באותה השורה. ואם בינתיים יתחלף ערכם של כל המשתנים הללו, זה כבר לא משנה. הערך שהיה אז, כבר קובע במשתנה $do וקושר לאותו הלחצן.

שורות 103-107

כאן מתבצעת שארית ההגדרה של הפריט Message. תצורת ההגדרות הללו אינן משתנות משורה לשורה, ולכן אינן כפופות לתנאי שהוצב לעיל.

שורה 110

כאן מתבצעת הצבה המקשרת את כל הפריטים שהוגדרו, לאובייקט המייצג את חלון התוצאות.

יצירת חלון הטופס הראשי (שורות 116-308):

שורות 116-119

יצירת החלון עצמו נקבעת כאן. המאפיינים פשוטים ביותר ואין צורך להרחיב עליהם.

שורות 124-137

מוגדר החלק שבו מזינים את חשבון המשתמש. פה מגדירים שני עצמים – תיבת טקסט עבור קלט, ותגית בצידה שתסביר מה להזין בתיבת הטקסט.

גם כאן ההגדרות פשוטות והמיקומים נקבעו בצורה מוחלטת.

שורות 142-212

מוגדרים 9 פריטים – 2 תגיות המסבירות כיצד להזין את הפלט, ושבעה פריטים מסוג תיבת סימון, המציינים כל אחד סוג מסוים של אירוע שרוצים לכלול בחיפוש.

גם כאן רוב ההגדרות פשוטות, וניתן לשים לב שפעם נוספת נקבע מיקום כל פריט, ביחס לפריט שקדם לו.

שורות 217-253

מוגדר אזור התאריך. גם כאן ההגדרות פשוטות והמיקום יחסי לפריטים אחרים בחלון.

שורות 256-257

מוסיפים ומקשרים את כל הפריטים שנוצרו והוגדרו, לאובייקט המייצג את חלון הטופס הראשי.

שורות 262-267

מוסיפים לחלון הראשי שני קווים. הקווים חוצצים בין שלושה אזורים שונים ומעניקים סדר למבנה הפריטים בחלון.

שורות 272-305

מגדירים את הלחצנים שבתחתית העמוד. כמו כן מוגדרת שם פעולתם של הלחצנים Close ו-Help.

שורה 308

נוספים פריטי הלחצנים ומקושרים לחלון הטופס הראשי.

הליך החיפוש והצגת התוצאות (שורות 313-400):

עיקר התהליך מתרחש עם הלחיצה על כפתור Find. שהרי זו מטרתו של הכלי – למצוא את האירועים על פי המסננים שהוגדרו.

כדי למצוא את האירועים, צריך תחילה לקבל את הקלט מן המשתמש. ולפני שמתחילים לעבוד איתו, צריך לאמת את הקלט על מנת לוודא שיש עם מה לעבוד – כדי לא ליפול בשגיאות.

היות והוגדר כי המסנן החשוב ביותר הוא טווח התאריכים, מסנן זה נבדק בכמה צורות.

טווח התאריכים הוא החשוב ביותר משתי סיבות:

1.     תאריך שלא הוגדר נכון, יהיה קשה מאוד לחפש ולהתאים תוצאות ללא שגיאה.

2.     אי אפשר לבטל סינון לפי תאריך, כפי שהוגדר בסינון של חשבונות או סוגי אירוע.

אם לא יוגדר סינון לתאריכים, התהליך יצטרך לחפש אירועים בכל הקבצים הקיימים במלאי. זה עלול להרחיב מאוד את טווח החיפוש ואת מספר התוצאות.

בקרה:

הבדיקה הראשונה עבור שדות התאריך, מתבצעת בשורות 317-321. שם מתבצעת בדיקה האם הוזנו שני תאריכים בפורמט המבוקש.

הבדיקה לוקחת כל אחד משדות התאריך, אוספת את תוכן השדה, ומנסה להמיר אותו לפורמט התאריך שאיתו עובד הכלי.

תיארוך:

מי שעוקב אחר הקוד של הכלי הזה על שני חלקיו, יבחין כי הוגדר פורמט אחיד לעבודה עם תאריכים.

הפורמט מופיע באורכים שונים. החל מאורך מלא שכולל חותמת זמן של תאריך ושעה – כולל השניות, וכלה באורך קצר שכולל רק את החודש ואת השנה.

השימוש בפורמט הזה עוזר להמיר את חותמות הזמן והתאריך ממחרוזת טקסט לאובייקט של תאריך – ולהיפך.

כאשר קיים אובייקט של תאריך, ניתן להפוך אותו למחרוזת בפשטות, באמצעות המתודה ToString(), שאליה שולחים את הפורמט שבתצורה שלו התאריך יוצג ויעבור לטקסט.

כאשר יש לנו את אותו הטקסט, אפשר להשתמש במתודה ParseExact() על מנת להפוך טקסט לאובייקט של תאריך.

כאן צריך להמנע משגיאות. ולכן לא יתבצע היפוך ישיר של הטקסט לתאריך, כי אולי תוכן תיבת הטקסט לא מתאים לפורמט והתהליך יקרוס או ישתבש.

לכן כל התהליך תלוי בתנאי.

התנאי בודק באמצעות המתודה TryParseExact() האם כל אחד משדות הקלט של התאריך, מכיל טקסט שניתן להמיר בצורה תקינה לפורמט המבוקש. אם הנסיון נכשל, אין שגיאה. פשוט עוברים לאפשרות האחרת שמציב התנאי.

ניתן לראות שהקריאה למתודה TryParseExact() לוקחת שורה וחצי בכל פעם. הכתיבה ארוכה כי הקריאה למתודה דורשת הזנה של ארבעה נתונים שכתיבתם ארוכה.

ניתן גם לראות שהתנאי מכיל את האופרטור and. זה אומר שכדי להמשיך הלאה, צריך לוודא תקינות של שני שדות התאריך. מספיק ששדה אחד הוזן בצורה לא תקינה, והתנאי יחליט שהבדיקה נכשלה ושעוברים לאפשרות האחרת.

אם הבדיקה עברה בהצלחה ונמצא כי תצורת התאריך תקינה, נאסף הקלט על מנת להגדיר את מסנני החיפוש (שורות 324-337).

שורות 324-325

נאספים כל פריטי תיבות הסימון, ואז עוברים במסננת לראות איזו תיבה מסומנת. לתוך משתנה מסוג מערך, נכנסים שמות האירועים המסומנים בתיבות.

שורות 327-328

נאסף הקלט מתוך שדות התאריך, ועובר המרה לאובייקט מסוג תאריך.

שורות 331-332

נאסף הקלט מתיבת הכתיבה של שם המשתמש, אל תוך מחרוזת. אם התיבה ריקה, נוצרת מחרוזת ריקה.

שורה 335

מתבצעת בדיקה על הנתונים שנאספו בשורות 324-325. אם נמצא כי לא סומנה אף תיבת סימון, זה אומר שאין סינון וכאילו סומנו כל התיבות. מה שאומר שבחיפוש צריך למצוא את כל סוגי האירועים.

שורה 337

משווים את האובייקטים שנלקחו והומרו משדות התאריך, על מנת לוודא שהתאריך הראשון אכן קודם לתאריך האחרון. אם התאריך האחרון קודם לראשון, קופצת הודעה למשתמש. לאחר מכן לתהליך אין הרבה מה לעשות והוא יסתיים בתוך שניה. אז המשתמש יוכל לתקן את התאריכים שהזין.

חיפוש וסינון:

בשלב זה יש כבר את כל הנתונים לחיפוש, ואפשר להתחיל.

דבר ראשון מגדירים את טווח החיפוש על פי תאריך. שכן טווח התאריכים קובע בכמה קבצים צריך לחפש (כל קובץ מכיל חודש של אירועים).

שורה 340

מוגדר מערך ריק עבור קבצים לחיפוש.

היות והקבצים בתיקיה נושאים תיוג של תאריכי חודשים, צריך להגדיר בצורה נכונה את הצורה שבה יבחרו הקבצים.

שורה 341

נלקח התאריך הראשון מן השניים שקובעים את מסגרת הזמן לחיפוש, ומתאריך הוא הופך למחרוזת המכילה רק את החודש והשנה.

אותה המחרוזת עוברת המרה מחדש (באותה השורה) לאובייקט של תאריך, בתצורה המכילה רק את החודש והשנה.

בדרך זו, כל תאריך של חודש מסוים, הופך לנקודת הזמן של השניה הראשונה באותו החודש. תאריך זה נכנס לתוך משתנה.

שורות 342-345

מתבצעת לולאה. הלולאה רצה כל עוד התאריך הזה של השניה הראשונה באותו החודש, איננו מאוחר יותר מהתאריך שהוגדר כגבול האחרון של מסגרת הזמן.

אם התאריך הנ"ל אינו חורג ממסגרת הזמן, תצורת התאריך המקבילה לשם הקובץ של אותו החודש, נוספת למערך משורה 340, כדי שיוכל למצוא אחר כך את הקובץ המדובר.

לאחר מכן נוסף חודש לאותו התאריך, שהופך לשניה הראשונה בחודש שלאחר מכן – ושוב מתבצעת הבדיקה.

הסבר:

בדרך זו, אם הטווח הוא בין 17-10 ועד 13-11, בתחילה נבדק חודש 10. האם השניה הראשונה בחודש 10 מאוחרת מן ה-13-11? לא. אז להוסיף את הקובץ של חודש 10 לרשימה, ואז להוסיף חודש לתאריך.

עכשיו הלולאה רצה סיבוב נוסף. אז נבדק האם השניה הראשונה בחודש 11 מאוחרת מן ה-13-11. עדיין לא. להוסיף גם את הקובץ של חודש 11 לרשימה, ולהוסיף חודש לתאריך.

סיבוב נוסף. האם השניה הראשונה בחודש 12 מאוחרת מן ה-13-11? כן. הלולאה מפסיקה לרוץ.

מה יש לנו? את הקבצים של החודשים 10 ו-11, שבשניהם יש אירועים במסגרת הזמן שהוגדרה.

ועם שמות הקבצים שנמצאו על פי הלולאה הקודמת, מתקדמים לשלב הבא.

שורה 348

מגדירים מערך ריק שיקלוט את כל האירועים המתאימים שיאספו מכל הקבצים.

שורות 349-356

מוגדרת לולאה שרצה על כל אחד משמות הקבצים שנאספו קודם, ומסננת מתוך הקובץ את האירועים המתאימים.

כיצד?

לוקחים את מערך שמות הקבצים שנאסף קודם, ועוברים בלולאה על כולם. קודם מתבצעת בדיקה בתיקיה שהוגדרה בשורה 9, האם קיים בה קובץ תואם לשם שאיתו רצה כרגע הלולאה.

אם כן, אוספים לתוך משתנה את כל האירועים המתאימים מתוך אותו הקובץ.

1.     תחילה מייבאים את כל האירועים מן הקובץ.

2.     אז מסננים את האירועים, ששם החשבון שלהם תואם לקלט שנאסף מתיבת הכתיבה של שם המשתמש. מחרוזת ריקה תתאים לכל השמות.

3.     מתוך כל האירועים המתאימים לשם המשתמש המבוקש, מסננים את אלה שקוד האירוע שלהם נמצא ברשימת הקודים שסומנו בשלב הקלט. אם אף אחד לא סומן, כבר הוגדר (שורה 335) להכניס לרשימה את כולם.

4.     מתוך כל האירועים שעדיין יש בידנו, צריך לאסוף את כל האירועים שחותמת הזמן שלהם מאוחרת יותר מהגבול הראשון, וגם קודמת לגבול האחרון של מסגרת הזמן. כלומר- אירועים שבתוך מסגרת הזמן שהוגדרה.

קבלת תוצאות:

את כל האירועים שנאספו לתוך אותו משתנה, מוסיפים למערך הכללי של האירועים, זה שהוגדר בשורה 348.

יש לשים לב שכל פקודת הסינון נכתבה בשורה אחת ארוכה. יכול להיות שמבחינת כתיבה נכונה היה עדיף לחלק את הסינון לשלוש פקודות נפרדות – לטובת הקריאות של הקוד. אבל רציתי להראות כאן משהו. ב-PowerShell, אם רוצים להמשיך את הקוד לשורה הבאה, משתמשים בגרש קטן מהכפתור שמשמאל לכפתור של הספרה 1, ומעל לחצן Tab. מציבים את הגרש בסוף השורה, ואז אפשר לרדת שורה ולהמשיך את הקוד כאילו עדיין נכתב באותה השורה.

לאחר שהלולאה סיימה לרוץ על כל שמות הקבצים ולאסוף מכולם את כל האירועים המתאימים למאפייני הסינון, קיימת רשימת אירועים שאותה אפשר להציג למשתמש.

יצירת חלון תוצאות (שורות 359-393):

שורה 359

מתבצעת קריאה לפונקציה PopResult, שהוגדרה ממש בתחילת הקוד. הפונקציה הזו יוצרת את החלון הבסיסי עבור תוצאות החיפוש.

שורה 360

מתבצעת קריאה לפונקציה AddLine שהוגדרה לעיל, כאשר מעבירים לה את המונה 0.

כזכור, הפונקציה AddLine מוסיפה שורות לחלון תוצאות החיפוש. מיקום השורה מבחינת הגובה, נקבע על פי מס' המונה של השורה. לכן זה הערך היחיד שנשלח לפונקציה.

כל שאר הפרמטרים מקבלים ערכי ברירת מחדל ויוצרים את שורת הכותרת.

שורות 363-369

אז נקבע ערך המונה על 1, ורצה לולאה על כל אחד מן האירועים ברשימת האירועים שנאספו.

על הלולאה:

בכל סיבוב של הלולאה מתבצעת קריאה לפונקציה, והקריאה שולחת לפונקציה את השדות מתוך הלוג, על מנת שתסדר אותם בשורה שנקבעת בחלון התוצאות.

כמו כן, ערכו של המונה נשלח לפונקציה, כדי לדעת באיזה גובה להציב את השורה החדשה.

אז המונה עולה במספר אחד, והלולאה ממשיכה לסיבוב הבא.

לאחר שהלולאה סיימה את כל הסיבובים שלה ונקבעו שורות בחלון התוצאות עבור כל האירועים שעברו את הסינון, נקבע גובהו של חלון התוצאות בהתאם למספר השורות שהוא מכיל.

שורות 374-375

נקבע תנאי שאומר שאם יש פחות מ-20 שורות, גובה החלון יצטמצם על מנת להכיל את השורות הקיימות. אחרת, גובה החלון יקבל ערך נקוב, וכדי להגיע לשורות אחרות יהיה אפשר לגלול למטה ולמעלה (בשורה 20 הוגדר סרגל גלילה אוטומטי עבור חלון התוצאות, שיכנס לפעולה במקרה הצורך).

שורות 378-392

אז מוגדר לחצן הסגירה עבור חלון התוצאות.

שורה 393

לאחר שחלון התוצאות הוגדר לחלוטין, רצה הפקודה שמראה אותו למשתמש.

בלוק מותנה:

כל הקוד שרץ עד עכשיו, היה במסגרת תנאי שהוגדר בשורות 317-321, וקבע שכל זה יעבוד רק אם שני שדות התאריך הוזנו בתאריכים בתצורה המתאימה.

שורות 395-399

מוגדר מה יקרה אם התנאי לא מתקיים – כלומר, לפחות אחד משדות התאריכים, לא קיבל קלט בתצורה המתאימה.

שורה 398

למעשה, קורה דבר אחד בלבד – שורה 398 מקפיצה חלון הודעה למשתמש, שעליו להזין את התאריך בתצורה הנכונה.

כאן מסתיימת הפעולה שרצה עם הלחיצה על כפתור Find. ולאחר שהסתיימה הגדרתו של הלחצן הזה, מופיעה הפקודה שמציגה את החלון הראשי.

נספחים:

נספח א' – הקוד:

כאן מופיע הקוד המלא של הקוד שבונה את הכלי.

# Add libraries required to run the GUI

Add-Type -AssemblyName System.Windows.Forms

[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

[System.Windows.Forms.Application]::EnableVisualStyles()

 

# Creates popup object which can pop text in a message box.

$pop = New-Object -ComObject Wscript.Shell

# The path to the location of the CSV files which contains the events.

$path = "\\SharedFolder\SubFolder\"

 

################################ Result Form ################################

 

# That function creates the base window of result. 

function PopResult {

   $global:Result = new-object Windows.Forms.Form

   $Result.Text = "Events Found"

   $Result.Width = 900

   $Result.Height = 50

   $Result.StartPosition = "CenterScreen"

   $Result.AutoScroll = $true   

}

 

################################ Function Result ################################

 

# This function takes each event from the search result, and prepares for it a line in the result window.

function AddLine {

   

   # Parameters received from the main proccess, with the detail of the event.

   # Parameters has default value for the 1st time running without input - for header line.

   param (

       $whole,

       $actby = "ActBy",

       $target = "Target",

       $res = "Result",

       $action = "Action",

       $time = "TimeCreated",

       $id = "EventID",

       $count

   )

   

   # Insert an action into a variable.

   # The action is to pop a message window contains the whole event message. 

   $do = {$pop.Popup($whole)}.GetNewClosure()

   

   # Define the coordinates where the result window start present the line.

   # The 1st line takes 10 pixels of space from the frame, and all lines takes 5 pixels from the line above.

   $l = 20

   $h = 10 + ($count * 25) 

 

   # The objects in that line.

   $tag_ActBy                       = New-Object system.Windows.Forms.Label

   $tag_ActBy.text                  = $ActBy

   $tag_ActBy.width                 = 120

   $tag_ActBy.height                = 20

   $tag_ActBy.location              = New-Object System.Drawing.Point($l, $h)

   $tag_ActBy.Font                  = New-Object System.Drawing.Font('tahoma',12)

 

   $tag_Target                      = New-Object system.Windows.Forms.Label

   $tag_Target.text                 = $target

   $tag_Target.width                = 120

   $tag_Target.height               = 20

   $tag_Target.location             = New-Object System.Drawing.Point(($tag_ActBy.Location.X + $tag_ActBy.Width + 10), $h)

   $tag_Target.Font                 = New-Object System.Drawing.Font('tahoma',12)

 

   $tag_Result                      = New-Object system.Windows.Forms.Label

   $tag_Result.text                 = $res

   $tag_Result.width                = 70

   $tag_Result.height               = 20

   $tag_Result.location             = New-Object System.Drawing.Point(($tag_Target.Location.X + $tag_Target.Width + 10), $h)

   $tag_Result.Font                 = New-Object System.Drawing.Font('tahoma',12)

 

   $tag_Action                      = New-Object system.Windows.Forms.Label

   $tag_Action.text                 = $action

   $tag_Action.width                = 140

   $tag_Action.height               = 20

   $tag_Action.location             = New-Object System.Drawing.Point(($tag_Result.Location.X + $tag_Result.Width + 10), $h)

   $tag_Action.Font                 = New-Object System.Drawing.Font('tahoma',12)

 

   $tag_Time                        = New-Object system.Windows.Forms.Label

   $tag_Time.text                   = $time

   $tag_Time.width                  = 190

   $tag_Time.height                 = 20

   $tag_Time.location               = New-Object System.Drawing.Point(($tag_Action.Location.X + $tag_Action.Width + 10), $h)

   $tag_Time.Font                   = New-Object System.Drawing.Font('tahoma',12)

 

   $tag_ID                          = New-Object system.Windows.Forms.Label

   $tag_ID.text                     = $id

   $tag_ID.width                    = 50

   $tag_ID.height                   = 20

   $tag_ID.location                 = New-Object System.Drawing.Point(($tag_Time.Location.X + $tag_Time.Width + 10), $h)

   $tag_ID.Font                     = New-Object System.Drawing.Font('tahoma',12)

 

   # If it's the first line - header line - #0, the colomn of Message contains header.

   if ($count -eq 0) {

       $Message                           = New-Object System.Windows.Forms.Label

   } else {

       # Else - Fore any line of event, Messege is a button to popup a window with the entire event message.

       $Message                           = New-Object System.Windows.Forms.Button

       # When clicking the button, do the action configured above (line 43).

       $Message.add_click($do)

   }

   # All the other proprties of Message are always the same.

   $Message.width                     = 80

   $Message.height                    = 20

   $Message.location                  = New-Object System.Drawing.Point(($tag_ID.Location.X + $tag_ID.Width + 10), $h)

   $Message.Font                      = New-Object System.Drawing.Font('tahoma',12)

   $Message.text                      = "Message"      

 

   # Add all objects to the base window of result.

   $Result.Controls.AddRange(@($tag_ActBy, $tag_Target, $tag_Result, $tag_Action, $tag_Time, $tag_ID, $Message))

}

 

################################ Form ################################

 

# Creates the base window of the GUI tool.

$GUI                               = New-Object system.Windows.Forms.Form

$GUI.ClientSize                    = New-Object System.Drawing.Point(750,400)

$GUI.text                          = "Show User Changes"

$GUI.TopMost                       = $false

 

################################ User ################################

 

# Creates the objects for the username part - where input the user which had been changed.

$user                              = New-Object system.Windows.Forms.TextBox

$user.multiline                    = $false

$user.width                        = 170

$user.height                       = 20

$user.location                     = New-Object System.Drawing.Point(134,25)

$user.Font                         = New-Object System.Drawing.Font('tahoma',10)

 

$tag_user                          = New-Object system.Windows.Forms.Label

$tag_user.text                     = "User Name"

$tag_user.AutoSize                 = $true

$tag_user.width                    = 35

$tag_user.height                   = 10

$tag_user.location                 = New-Object System.Drawing.Point(40,25)

$tag_user.Font                     = New-Object System.Drawing.Font('tahoma',12)

 

################################ Events ################################

 

# Creates the objects for the event types part - where checking the boxs of the events to find.

$tag_eventid                       = New-Object system.Windows.Forms.Label

$tag_eventid.text                  = "Select The type of change you want to monitor:"

$tag_eventid.AutoSize              = $true

$tag_eventid.width                 = 100

$tag_eventid.height                = 10

$tag_eventid.location              = New-Object System.Drawing.Point($tag_user.Location.X,($tag_user.Location.Y + 40))

$tag_eventid.Font                  = New-Object System.Drawing.Font('tahoma',12)

 

$tag_selectall                     = New-Object system.Windows.Forms.Label

$tag_selectall.text                = " - Keep empty if you want to select all"

$tag_selectall.AutoSize            = $true

$tag_selectall.width               = 100

$tag_selectall.height              = 10

$tag_selectall.location            = New-Object System.Drawing.Point($tag_eventid.Location.X,($tag_eventid.Location.Y + 25))

$tag_selectall.Font                = New-Object System.Drawing.Font('tahoma',10, [System.Drawing.FontStyle]::Italic)

 

$4720                              = New-Object system.Windows.Forms.CheckBox

$4720.text                         = "4720 - A user account was created"

$4720.AutoSize                     = $false

$4720.width                        = 500

$4720.height                       = 20

$4720.location                     = New-Object System.Drawing.Point($tag_eventid.Location.X,($tag_selectall.Location.Y + 30))

$4720.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4722                              = New-Object system.Windows.Forms.CheckBox

$4722.text                         = "4722 - A user account was enabled"

$4722.AutoSize                     = $false

$4722.width                        = 500

$4722.height                       = 20

$4722.location                     = New-Object System.Drawing.Point($4720.Location.X,($4720.Location.Y + 30))

$4722.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4725                              = New-Object system.Windows.Forms.CheckBox

$4725.text                         = "4725 - A user account was disabled"

$4725.AutoSize                     = $false

$4725.width                        = 500

$4725.height                       = 20

$4725.location                     = New-Object System.Drawing.Point($4722.Location.X,($4722.Location.Y + 30))

$4725.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4723                              = New-Object system.Windows.Forms.CheckBox

$4723.text                         = "4723 - An attempt was made to change an account's password"

$4723.AutoSize                     = $false

$4723.width                        = 500

$4723.height                       = 20

$4723.location                     = New-Object System.Drawing.Point($4725.Location.X,($4725.Location.Y + 30))

$4723.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4724                              = New-Object system.Windows.Forms.CheckBox

$4724.text                         = "4724 - An attempt was made to reset an account's password"

$4724.AutoSize                     = $false

$4724.width                        = 500

$4724.height                       = 20

$4724.location                     = New-Object System.Drawing.Point($4723.Location.X,($4723.Location.Y + 30))

$4724.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4738                              = New-Object system.Windows.Forms.CheckBox

$4738.text                         = "4738 - A user account was changed"

$4738.AutoSize                     = $false

$4738.width                        = 500

$4738.height                       = 20

$4738.location                     = New-Object System.Drawing.Point($4724.Location.X,($4724.Location.Y + 30))

$4738.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

$4781                              = New-Object system.Windows.Forms.CheckBox

$4781.text                         = "4781 - The name of an account was changed"

$4781.AutoSize                     = $false

$4781.width                        = 500

$4781.height                       = 20

$4781.location                     = New-Object System.Drawing.Point($4738.Location.X,($4738.Location.Y + 30))

$4781.Font                         = New-Object System.Drawing.Font('tahoma',12)

 

################################ Date ################################

 

# Creates the objects for the dates part - where input the date range of time to search for events.

$tag_date_format                   = New-Object system.Windows.Forms.Label

$tag_date_format.text              = "Insert date in `ndd/MM/yyyy format"

$tag_date_format.AutoSize          = $true

$tag_date_format.width             = 35

$tag_date_format.height            = 10

$tag_date_format.location          = New-Object System.Drawing.Point(($tag_user.Location.X + 510), $tag_user.Location.Y)

$tag_date_format.Font              = New-Object System.Drawing.Font('tahoma',12)

 

$tag_date_from                     = New-Object system.Windows.Forms.Label

$tag_date_from.text                = "Date - From:"

$tag_date_from.AutoSize            = $true

$tag_date_from.width               = 35

$tag_date_from.height              = 10

$tag_date_from.location            = New-Object System.Drawing.Point($tag_date_format.Location.X, $4720.Location.Y)

$tag_date_from.Font                = New-Object System.Drawing.Font('tahoma',12)

 

$date_from                         = New-Object system.Windows.Forms.TextBox

$date_from.multiline               = $false

$date_from.width                   = 170

$date_from.height                  = 20

$date_from.location                = New-Object System.Drawing.Point($tag_date_format.Location.X, $4722.Location.Y)

$date_from.Font                    = New-Object System.Drawing.Font('tahoma',10)

 

$tag_date_to                       = New-Object system.Windows.Forms.Label

$tag_date_to.text                  = "Date - To:"

$tag_date_to.AutoSize              = $true

$tag_date_to.width                 = 35

$tag_date_to.height                = 10

$tag_date_to.location              = New-Object System.Drawing.Point($tag_date_format.Location.X, $4723.Location.Y)

$tag_date_to.Font                  = New-Object System.Drawing.Font('tahoma',12)

 

$date_to                           = New-Object system.Windows.Forms.TextBox

$date_to.multiline                 = $false

$date_to.width                     = 170

$date_to.height                    = 20

$date_to.location                  = New-Object System.Drawing.Point($tag_date_format.Location.X, $4724.Location.Y)

$date_to.Font                      = New-Object System.Drawing.Font('tahoma',10)

 

# Add all objects configured above, to the GUI tool window.

$GUI.controls.AddRange(@($user, $tag_user, $tag_eventid, $tag_selectall, $4720, $4722, $4725, $4723, $4724, $4738, $4781,` 

$tag_date_format, $tag_date_from, $date_from, $tag_date_to, $date_to))

 

################################ Line ################################

 

# Add lines to the GUI tool window, to separate between sections of objects.

$line = $GUI.createGraphics()

$pen = new-object Drawing.Pen black

# Vertical

$GUI.add_paint({$line.DrawLine($pen, ($tag_user.location.X + 500), ($tag_user.location.y), ($tag_user.location.X + 500), ($4781.location.y + 20))})

# Under UserName - Horizonal.

$GUI.add_paint({$line.DrawLine($pen, ($tag_user.location.X), ($tag_user.location.y + 30), ($tag_user.location.X + 500), ($tag_user.location.y + 30))})

 

################################ Buttons ################################

 

# Creates the buttons at the bottom of the window.

$Find                              = New-Object System.Windows.Forms.Button

$Find.width                        = 80

$Find.height                       = 40

$Find.location                     = New-Object System.Drawing.Point(245, ($4781.location.y + 50))

$Find.Font                         = New-Object System.Drawing.Font('tahoma',12)

$Find.text                         = "Find"

 

$Close                             = New-Object System.Windows.Forms.Button

$Close.width                       = 80

$Close.height                      = 40

$Close.location                    = New-Object System.Drawing.Point((($Find.Location.X) + 90), ($4781.location.y + 50))

$Close.Font                        = New-Object System.Drawing.Font('tahoma',12)

$Close.text                        = "Close"

# To cleare memory space and avoid the powershell to get stuck, close the form and reset all of it's content 

# to a simple variable - while clicking the Close button. 

$Close.Add_Click({

   $GUI.Close()

   $GUI = 0

})

 

$Help                              = New-Object System.Windows.Forms.Button

$Help.width                        = 80

$Help.height                       = 40

$Help.location                     = New-Object System.Drawing.Point((($Close.Location.X) + 90), ($4781.location.y + 50))

$Help.Font                         = New-Object System.Drawing.Font('tahoma',12)

$Help.text                         = "Help"

 

<# You can add Explanation of the proccess and the ways to use it into a text file.

Place that text file in the same folder with the CSV files. Than, anytime clicking the button Help, the text file will open.

If there is no such file in that location, nothing will happend. #> 

$Help.Add_Click({

   if (Test-Path ($path + "Help.txt")){

       Start-Process ($path + "Help.txt")}

})

 

# Add buttons to the GUI tool window.

$GUI.Controls.AddRange(@($Find, $Close, $Help))

 

################################ Main Process ################################

 

# Add Button event - What's happend when clocking the button Find. 

$Find.Add_Click({ 

   

   # Check if the dates input is there, and in the correct format.

   # If it's correct, continue with the proccess.

   if (([DateTime]::TryParseExact($date_from.Lines[0], 'dd/MM/yyyy', [System.Globalization.CultureInfo]::InvariantCulture, `

   [System.Globalization.DateTimeStyles]::None, [ref](Get-Date))) `

    -and `

    ([DateTime]::TryParseExact($date_to.Lines[0], 'dd/MM/yyyy', [System.Globalization.CultureInfo]::InvariantCulture, `

    [System.Globalization.DateTimeStyles]::None, [ref](Get-Date)))) {

       

       # Put all events type check boxes into arraym and get thos which are checked.

       $events = $4720, $4722, $4725, $4723, $4724, $4738, $4781

       $checked = $events | ?{$_.checked -eq $true} | %{$_.text.substring(0,4)}

       # Get the strings from the date input boxes, and convert it to date objects.

       $from = [datetime]::parseexact($date_from.Lines[0], 'dd/MM/yyyy', $null)

       $to = [datetime]::parseexact($date_to.Lines[0], 'dd/MM/yyyy', $null)

       

       # Get the user name from it's input box into a string. If the box is empty, create empty string. 

       if ($user.Lines[0] -ne $null) {$userinput = $user.Lines[0]} 

       else {$userinput = ""}

               

       # If no event type had been chosen, consider all as checked.

       if ($checked -eq $null) {$checked = $events | %{$_.text.substring(0,4)}}

       # If last date in time range found as earlier than first date, popup message box to the user to insert the date again.

       if ($to -lt $from) {$pop.Popup("Last date is earlier than first date!")}

       

       # Get all the files that contains event from the time range defined by the user.

       $files = @()

       $from1 = [datetime]::parseexact(($from.tostring('MM-yyyy')), 'MM-yyyy', $null)

       while (!($from1 -gt $to)) {

           $files += $from1.tostring('MM-yyyy')

           $from1 = $from1.AddMonths(1)   

       }

 

       # Loop over all the files found above, and from each, collect ell event within the time range.

       $all = @()

       foreach ($file in $files) {

           if (Test-Path ($path + $file + ".csv")) {

               $logs = Import-Csv -path ($path + $file + ".csv") | ?{$_.target -match $userinput} | ?{$_.id -in $checked.split(" ")} | `

               ?{(([datetime]::parseexact($_.TimeCreated.Split(" ")[0], 'dd/MM/yyyy', $null)) - $from) -ge 0 `

               -and ($to - ([datetime]::parseexact($_.TimeCreated.Split(" ")[0], 'dd/MM/yyyy', $null))) -ge 0}

               $all += $logs

           }

       }

 

       # Create the form of the result window, and insert the header line.

       PopResult

       AddLine -count 0 

 

       # Loop over all events found in the time range, and create a line for each, in the result window.

       $count = 1 

       foreach ($x in $all){

           AddLine -whole $x.Message -actby $x.ActBy -target $x.Target -res ($x.Result.Split(" ")[1]) `

           -action ($x.Action.Split(" ")[5] + " " + $x.Action.Split(" ")[-1].substring(0, 8)) `

           -time $x.TimeCreated -id $x.Id -count $count

           $count ++

       }

       

       <# Configure the size of the result window according to the numbers of lines in it.

       Up to 20 lines, the window hight determained to wrap the lines. From 20 lines and above, the size of the window is set, 

       and the user can scroll down to the other lines. #>

       if ($count-lt 20) {$Result.Height = (($count * 25) + 130)}  

       else {$Result.Height = 800} 

 

       # Create Close button to the result window.

       $resclose                          = New-Object System.Windows.Forms.Button

       $resclose.width                    = 80

       $resclose.height                   = 40

       $resclose.location                 = New-Object System.Drawing.Point((($Result.Width - $resclose.Width) / 2), (($count * 25) + 40))

       $resclose.Font                     = New-Object System.Drawing.Font('tahoma',12)

       $resclose.text                     = "Close"

       

       # The Close button closes the form and reset the complex object of the form to small and simple variable of integer.

       $resclose.Add_Click({

           $Result.Close()

           $Result = 0

       })

 

       # Add the Close button to the result window form objects, than present the window.

       $Result.Controls.Add($resclose)

       $Result.ShowDialog()

         

   } else {

       # If the dates from the input boxs arn't in the correct format, 

       # or it's empty - popup a messege and do nothing else. 

       $pop.Popup("You must insert dates! `nPlease insert date in the right format!")

   }

})

 

[void]$GUI.ShowDialog() .

נספח ב' – תוכן עבור קובץ Help:

את תוכן הנספח הזה מדביקים בקובץ txt ושמים בתיקיה של הכלי עם קובץ הקוד.

צריך לשייך לקובץ את ההפניה בפעולה של הלחצן Help.

 Created by Yosi Cohen

https://www.linkedin.com/in/yosi-cohen/

 

==========================================================

 

This tool designed to find and track changes on Active Directory Accounts.

 

Use cases for this tool:

Sometimes you find something weird or something that isn't according to the organization policy, 

or that isn't by the convention you use to work with. 

This tool will help you to find out who did it and when.

 

==========================================================

 

Using the mean window:

You can track accounts changes, according to 3 search filters:

1. Account Name (refer as User Name).

2. Event type - which type of change you are looking for.

3. Time range.

 

The first 2 filters aren't necessary and you can work without it. If you live it empty, there 

will be no filter in this field. 

But the date filter is a must, and the process will not start without a time range to search 

within its boundaries.

 

The time Range is the most important key, and it's must be there in the correct format mentioned 

in the GUI. 

Therefore, before the process will run to find result, variety of checks are performed to make 

it certain that the date inserted can make a time frame that the process can work with.

 

After the user inserts his desired filters, he can press Find button to get the result.

Then a result window will pop and present the result in a format of table, where each line 

represents an event.

 

The user can press Close button to end the process and close the window, and reset all the variables 

and memory space required for it.

 

The last button is Help, intended to show this information about the tool and how to use it.

 

==========================================================

 

Get the result window:

After the user had configured the search filters and place the dates correct for time range, 

he can press Find button to show the result window.

Note that if the filter covers too many results, it will take a while to build the result window for it.

If the search is well filtered and the result are few, it will popup right away.

 

==========================================================

 

The structure of the result window:

The result window is structured as a table.

There is the header, and every event found by the search get its own line.

 

Each line contains these elements:

1. ActBy - The account who made that change and cause that event.

2. Target - The account that had been changed.

3. Result - Indicates if the action end with success or a failure.

4. Action - A word or 2 to describe the action on that account.

5. TimeCreated - Timestamp of the moment of the event.

6. Event - The EventID - The code number of the action made.

7. Message - The entire log message of that event.

  Sometimes the information from the fields above is not enough.

  Sometimes you want to read the entire log message - mostly in an event type 

  of "Account was changed". In this case you want to check which attribute had change,

  and this information appear in the log message.

  

  Message is a button to click, and click on it will pop up a message box contains the

  entire log message.

  

The height of the result window depends on the number of the lines.

Up to 20 lines, the height of the window will grow to wrap the lines. 

From 20 lines and above, the height of the window will set, and the user can scroll up and down to see 

another lines. 

 

At the bottom of the result window there is a Close button, to close the window and the form object. 

It's to reset the big and complex variable of the form, to a simple and small variable of an integer.

When there are 20 lines and more, the user need to scroll down to find the Close button.

נספח ג' – הפיכת הסקריפט לקובץ שמריצים מתוך אייקון:

1.     שומרים את כל הקוד בתוך קובץ PowerShell – סיומת ps1.

2.     הולכים למיקום שבו רוצים להציב את האייקון שיריץ את הכלי – נגיד, שולחן העבודה.

קליק ימני עם העכבר על שולחן העבודה או על שטח בתיקיה > חדש/New > קיצור דרך/Shortcut.

3.     אז יפתח חלון שבו צריך להזין את הנתיב שאליו יפנה קיצור הדרך. צריך להכניס שם את הטקסט הבא:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -file "c:\folder\ScriptFile.ps1"

4.     להחליף את המחרוזת בסוף בתוך הסוגריים (לאחר הפרמטר file) למיקום בו יושב הקובץ בו שמור הקוד.

5.     לאשר ולסגור.

6.     לפתוח את המאפיינים של קיצור הדרך, לגשת ללשונית קיצור דרך/Shortcut, ובשדה התחל ב/Start in להציב את הנתיב לקובץ ההפעלה של Powershell:

C:\Windows\System32\WindowsPowerShell\v1.0

7.     מעט למטה מזה ללחוץ על שינוי סמל…/Change Icon, ולבחור באיזה אייקון שרוצים שיופיע על קיצור הדרך.

יש הרבה אתרים שבאמצעותם ניתן להפוך קבצי תמונה לקובץ אייקון קטן שמתאים, כך שאפשר ליצור איזה סמל שרוצים עבור הכלי.

8. לאשר ולסגור.

9. לא לשכוח לשמור את תוכנו של נספח ב' לתוך קובץ טקסט בשם Help.txt ולהציב אותו בתיקיה שבה יושבים קבצי ה-CSV שאיתם עובד הכלי.

כתיבת תגובה