diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitBanner.png b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitBanner.png new file mode 100644 index 0000000..222bca4 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitBanner.png differ diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitConfig.xml b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitConfig.xml new file mode 100644 index 0000000..e71f7c5 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitConfig.xml @@ -0,0 +1,1203 @@ + + + + + + 3.8.3 + 30/09/2020 + + + + + True + + $envTemp + + HKLM:\SOFTWARE + + $envWinDir\Logs\Software + + $envTemp + + HKCU:\SOFTWARE + + $envProgramData\Logs\Software + + False + + CMTrace + + False + + 10 + + True + + + + + + + AppDeployToolkitLogo.ico + + AppDeployToolkitBanner.png + + 70 + + + + + + + /L*v + + $envWinDir\Logs\Software + + $envProgramData\Logs\Software + + REBOOT=ReallySuppress /QB-! + + REBOOT=ReallySuppress /QN + + REBOOT=ReallySuppress /QN + + 600 + + + + + + + + + True + + 6900 + + 1618 + + 60012 + + 60 + + 600 + + 120 + + True + + + + + 2 + + + + + + + + You do not have enough disk space to complete the installation of: + {0} + + Space required: {1}MB + Space available: {2}MB + + Please free up enough disk space in order to proceed with the installation. + Continue + + Only select "Continue" after closing the above listed application(s). + + Close Programs + + Defer + + The following programs must be closed before the installation can proceed. + + Please save your work, close the programs, and then continue. + Alternatively, save your work and click "Close Programs". + + NOTE: The program(s) will be automatically closed in: + + The following application is about to be installed: + + You can choose to defer the installation until the deferral expires: + + Once the deferral has expired, you will no longer have the option to defer. + + Remaining Deferrals: + + Deadline: + + + The {0} will automatically continue in: + + + + + Installation + + Uninstallation + + Repairing + + + + started. + + complete. + + complete. A reboot is required. + + failed. + + not complete. + + + Installation in progress. Please wait... + + Uninstallation in progress. Please wait... + + Repair in progress. Please wait... + + Launching this application has been temporarily blocked so that an installation operation can complete. + + Restart Required + + In order for the installation to complete, you must restart your computer. + Please save your work and restart within the allotted time. + Your computer will be automatically restarted at the end of the countdown. + + Time remaining: + Minimize + Restart Now + + + + + Du har ikke plads nok til at færdiggøre installationen af: + {0} + + Plads krævet: {1}MB + Plads tilgængelig: {2}MB + + Vær venlig at frigøre nok diskplads før du fortsætter installationen. + Fortsæt + Vælg kun "Fortsæt" efter at du har afsluttet de ovenfor nævnte programmer. + Luk Programmer + Udsæt + Følgende programmer skal lukkes før installationen kan fortsætte. + + Gem dit arbejde, luk programmerne og fortsæt. + Alternativt kan du gemme dit arbejde og trykke på "Luk Programmer". + BEMÆRK: Programmet/Programmerne vil automatisk blive lukket om: + Følgende applikation vil nu blive installeret: + Du kan vælge at udsætte installationen indtil udsættelsesperioden udløber: + Når udsættelsesperioden udløber kan du ikke længere udsætte installationen. + Udsættelser tilbage: + Deadline: + + The {0} will automatically continue in: + + + Installation + Afinstallation + Reparere + + startet. + færdig. + færdig. En genstart er nødvendig. + fejlet. + ikke færdig. + Installation i gang. Vent venligst... + Afinstallation i gang. Vent venligst... + Reparere i gang. Vent venligst... + Opstart af denne applikation er midlertidigt blokeret da en installationsproces er under afvikling. + Genstart Nødvendig + For at færdiggøre installationen skal du genstarte din computer. + Du bør venligst gemme dit arbejde og genstarte indenfor det givne tidsrum. + Din computer vil automatisk blive genstartet når nedtællingen er færdig. + Tid tilbage: + Minimere + Genstart Nu + + + + + Vous n’avez pas assez d’espace sur le disque pour compléter l’installation de: + {0} + + Espace requis: {1}MB + Espace disponible: {2}MB + + Merci de vous assurez d’avoir assez d’espace libre pour pouvoir continuer l’installation. + Poursuivre + Veuillez cliquer sur « Poursuivre » uniquement après avoir fermé la ou les application(s) ci-dessus. + Fermer Programmes + Reporter l'installation + Les programmes suivants doivent être fermés afin que l'installation s'initialise. + + Merci de sauvegarder votre travail, fermer tous les programmes, et continuer. + Vous pouvez aussi sauvegarder votre travail puis cliquez sur « Fermer Programmes ». + REMARQUE: Les programmes seront automatiquement fermés dans: + L'application suivante est sur le point d'être installée: + Vous pouvez choisir de reporter l'installation: + Quand le temps aura expiré, vous n'aurez plus la possibilité de reporter. + Nombre(s) de report restant(s): + Temps limite: + + The {0} will automatically continue in: + + + Installation + Désinstallation + Réparation + en cours. + réussie. + réussie. Un redémarrage est requis. + en échec. + incomplète. + Installation en cours, merci de patienter... + Désinstallation en cours, merci de patienter... + Réparation en cours, merci de patienter... + Le lancement de cette application a été temporairement bloqué afin qu'une autre installation puisse se terminer. + Redémarrage Requis + Pour que l'installation soit compléte, vous devez redémarrer votre ordinateur. + Merci de sauvegarder votre travail et de redémarrer avant que le temps spécifié ne soit écoulé. + Votre ordinateur sera automatiquement redémarré à la fin du décompte. + Temps restant: + Minimiser + Redémarrer Maintenant + + + + + Sie haben nicht genug freien Speicherplatz um die Installation abzuschließen: + {0} + + Platzbedarf: {1}MB + Freier Speicherplatz: {2}MB + + Bitte geben Sie ausreichend Speicherplatz frei, um mit der Installation fortzufahren. + Weiter + Klicken Sie erst auf „Weiter“, nachdem Sie die obigen Anwendung(en) geschlossen haben. + Programme Schließen + Aufschieben + Die folgenden Programme müssen geschlossen werden, bevor die Installation fortgesetzt werden kann. + + Bitte speichern Sie Ihre Arbeit, schließen Sie die Programme und fahren Sie dann fort. + Alternativ können Sie Ihre Arbeit speichern und dann auf „Programme Schließen“ klicken. + HINWEIS: Diese Programme werden automatisch geschlossen: + Die folgende Anwendung soll installiert werden: + Sie können die Installation verzögern, bis die Rückstellung abläuft: + Sobald die Rückstellung abgelaufen ist, werden Sie keine Möglichkeit mehr haben die Installation zu verschieben. + Verbleibende Rückstellungen: + Termin: + + Die {0} wird automatisch fortgesetzt in: + + + Installation + Deinstallation + Reparatur + gestartet. + abgeschlossen. + abgeschlossen. Ein Neustart ist erforderlich. + ist fehlgeschlagen. + nicht abgeschlossen werden. + Installation wird durchgeführt. Bitte warten... + Deinstallation wird durchgeführt. Bitte warten... + Reparatur wird durchgeführt. Bitte warten... + Das Starten dieser Anwendung(en) wurde vorübergehend blockiert, damit der Installationsvorgang erfolgreich durchgeführt werden kann. + Neustart Erforderlich + Zum Abschluss der Installation müssen Sie Ihren Computer neu starten. + Bitte speichern Sie Ihre Arbeit und starten Sie den Computer innerhalb der vorgegebenen Zeit neu. + Am Ende des Countdowns wird Ihr Computer automatisch neu gestartet. + Verbleibende Zeit: + Minimieren + Jetzt Neustarten + + + + + Non si dispone di spazio su disco sufficiente per completare l' installazione di: + {0} + + Spazio necessario: {1}MB + Spazio disponibile: {2}MB + + Si prega di spazio libero su disco sufficiente per procedere con l'installazione. + Continua + Seleziona "Continua" solo dopo la chiusura della(e) applicazione(i) elencate sopra. + Chiudi Programmi + Rimanda + I seguenti programmi devono essere chiusi prima che l'installazione possa procedere. + + Salvare il lavoro , chiudere i programmi, e poi continuare. + In alternativa, salvare il lavoro e fare clic su "Chiudi Programmi". + NOTA: il programma(s) sarà chiuso automaticamente in: + La seguente applicazione sta per essere installata: + Si può decidere di posticipare l'installazione fino alla prossima richiesta automatica: + Una volta che le richieste rimanenti saranno scadute, non sarà più possibile posticipare l'installazione. + Posticipi rimanenti: + Scadenza: + + The {0} will automatically continue in: + + + Installazione + Disinstallazione + Riparazione + Iniziata. + Completata. + Completata. È necessario riavviare il computer. + Fallita. + Non completata. + Installazione in corso. Attendere prego... + Disinstallazione in corso. Attendere prego... + Riparazione in corso. Attendere prego... + L'esecuzione di questa applicazione è stata temporaneamente bloccata in modo che l'operazione di installazione possa essere completata. + Riavvio Richiesto + Per completare l'installazione, è necessario riavviare il computer. + Salvare il lavoro e riavviare entro il tempo assegnato. + Il computer verrà riavviato automaticamente al termine del conto alla rovescia. + Tempo rimanente: + Minimizzare + Riavvia Ora + + + + + ディスクの空き容量が不足しているため、インストールを完了できません: + {0} + + 必要な容量: {1}MB + 現在の空き容量: {2}MB + + インストールを実行するために、容量を確保してください + 続行 + 上記に記載されているアプリケーションを終了してから「続ける」を選択してください。 + プログラムを強制終了 + 後で + インストールを実行するために、下記のプログラムを閉じる必要があります。 + + 実行中のアプリケーションを保存し、閉じてから続行してください。 + または、実行中のアプリケーションを保存し、プログラムを強制終了ボタンをクリックしてくだい + 注意: これらのプログラムは自動的に閉じられます: + このアプリケーションはこれからインストールされます。 + 再試行可能回数が0になるまでは、都合の良い時にインストール可能です。 + 再試行可能回数が0になった場合、システムで強制インストールをします。 + 再試行可能回数: + デッドライン: + + The {0} will automatically continue in: + + + インストール + アンインストール + 修復 + 開始 + 完了です + 完了。再起動が必要です。 + 失敗。 + 未完了。 + インストール中です。 少々お待ちください。 + アンインストール中です。 少々お待ちください。 + 修復中です。 少々お待ちください。 + アプリケーションインストールが完了するまで、このアプリケーションの起動を一時的にブロックしています。 + 再起動が必要です + インストールを完了するために、再起動が必要です。 + 実行中のアプリケーションを保存し、再起動してください。 + カウントダウン後にコンピュータが再起動します。 + 残時間: + 最小 化 + 今すぐ再起動 + + + + + Du har ikke nok diskplass for å fullføre installasjonen av: + {0} + + Ledig plass påkrevd: {1}MB + Ledig plass tilgjengelig: {2}MB + + Frigjør diskplass for å fortsette installasjonen. + Fortsett + Velg kun "Fortsett" etter du har lukket applikasjonen(e) oppført over. + Lukk Programmer + Utsett + Følgende programmer må lukkes før installasjonen kan fortsette. + + Lagre arbeidet, lukk programmene og velg "Fortsett" + Eller velg "Lukk Programmer" uten å lagre. + OBS: Programmet vil automatisk lukkes om: + Følgende program vil bli installert: + Du kan velge å utsette installasjonen et begrenset antall ganger inntil fristen utløper: + Når fristen har utløpt kan du ikke lenger utsette installasjonen. + Gjenstående utsettelser: + Frist: + + The {0} will automatically continue in: + + + Installasjon + Avinstallasjon + Reparasjon + startet. + fullført. + ferdig. En omstart er nødvendig. + mislyktes. + ikke fullført. + Programvareinstallasjon pågår. Vennligst vent.. + ProgramvareAvinstallasjon pågår. Vennligst vent.. + ProgramvareReparasjon pågår. Vennligst vent.. + Start av dette programmet er midlertidig blokkert til programvareinstallasjon er ferdig. + Omstart Kreves + En omstart av pcen er nødvendig for å fullføre installasjonen. + Lagre arbeidet ditt og gjør en omstart av pc innen fristen. + Pcen vil automatisk starte på nytt, når nedtellingen er slutt. + Tid som gjenstår: + Minimere + Omstart Nå + + + + + Er is onvoldoende schijfruimte voor de installatie van: + {0} + + Ruimte nodig: {1}MB + Ruimte beschikbaar: {2}MB + + Gelieve voldoende schijfruimte vrij te maken om de installatie te starten. + Doorgaan + Selecteer alleen 'Doorgaan' na het sluiten van de bovenstaande toepassing(en). + Sluit Applicaties + Uitstel + De volgende applicaties moeten afgesloten worden om de installatie te voltooien. + + Sla je werk op, sluit de applicaties, en ga verder. + Of, sla je werk op en klik op 'Sluit Applicaties'. + LET OP: De applicatie(s) worden afgesloten over: + De volgende applicatie wordt zometeen geïnstalleerd: + Je kan de installatie uitstellen tot het maximale uitsteltermijn is verstreken: + Na verstrijken van het uitsteltermijn is deze optie niet langer beschikbaar. + Aantal keer uitstellen: + Deadline: + + De {0} gaat automatisch door over: + + + Installatie + Verwijderen + Reparatie + gestart + voltooid + voltooid. Een herstart is nodig + gefaald + onvolledig + Installatie bezig. Even geduld... + Verwijderen bezig. Even geduld... + Reparatie bezig. Even geduld... + Het opstarten van deze applicatie werd tijdelijk geblokkeerd om een installatie uit te voeren. + Herstart nodig + Om de installatie te voltooien is een herstart nodig. + Gelieve je werk op te slaan en binnen het toegestane termijn de computer herstarten + De computer zal herstarten als de teller op nul staat + Resterende tijd: + Minimaliseren + Herstart Nu + + + + + Brak miejsca na dysku: + {0} + + Potrzeba: {1}MB + Obecnie wolnego miejsca: {2}MB + + Proszę zwiększyć ilość miejsca usuwając zbędne pliki. + Kontynuuj + Tylko wybrać „Kontynuuj” po zamknięciu wyżej wymienione aplikacje. + Zamknij Programy + Odłóż + Następujące programy muszą zostać zamknięte przed rozpoczęciem instalacji. + + Proszę zapisać wszystkie dokumenty i zamknąć programy, a następnie kliknąć przycisk „Kontynuuj”. + Alternatywnie zapisz wszystkie dokumenty i kliknij przycisk „Zamknij Programy”. + UWAGA: Programy zostaną automatycznie zamknięte za: + Zostanie zainstalowana następująca aplikacja: + Instalacja może zostać przełożona na późniejszy termin. + Jeżeli zostanie przekroczona możliwa ilość przełożeń, opcja „Odłóż” będzie niedostępna. + Pozostała ilość przełożeń instalacji: + Ostateczny termin instalacji: + + The {0} will automatically continue in: + + + Instalacja + Deinstalacja + Naprawa + rozpoczęta. + zakończona. + zakończona. Wymagany jest restart komputera. + nie powiodła się. + nieukończona. + Trwa instalacja. Proszę czekać... + Trwa deinstalacja. Proszę czekać... + Trwa naprawa. Proszę czekać... + Uruchomienie tej aplikacji zostało zablokowane na okres instalacji. + Wymagany Restart + Aby instalacja została poprawnie ukończona wymagany jest restart komputera. + Proszę zapisać wszystkie dokumenty i zrestartować komputer w wyznaczonym czasie. + Komputer zostanie automatycznie zrestartowany po upływie wyznaczonego czasu. + Pozostały czas do restartu automatycznego: + Zminimalizować + Restartuj Teraz + + + + + Você não tem espaço em disco suficiente para concluir a instalação de: + {0} + + Espaço necessário: {1}MB + Espaço disponível: {2}MB + + Por favor, espaço livre em disco suficiente, a fim de prosseguir com a instalação. + Continuar + Selecione "Continuar" somente após fechar a(s) aplicação(ões) listada(s) abaixo. + Fechar Programas + Adiar + Programas de seguir devem ser fechados antes que a instalação possa prosseguir. + + Por favor, guarde o seu trabalho, feche os programas e em seguida continuar. + Como alternativa, salve seu trabalho e clique em "Fechar Programas". + NOTA: O programa será fechado automaticamente em: + O seguinte aplicativo está prestes a ser instalado: + Você pode optar por adiar a instalação até que expire o diferimento: + Uma vez que o diferimento expirou, você já não terá a opção de adiar a. + Restantes diferimentos: + Prazo: + + The {0} will automatically continue in: + + + Instalação + Desinstalação + Reparação + começou a. + completo. + completa. Uma reinicialização é necessária. + falhou. + não completar. + Instalação em andamento. Por favor aguarde... + Desinstalação em andamento. Por favor aguarde... + Reparação em andamento. Por favor aguarde... + Lançar este aplicativo está temporariamente bloqueado para que possa concluir uma operação de instalação. + Reinicialização Necessária + Em ordem para completar a instalação, você deve reiniciar seu computador. + Por favor, salve o trabalho e reiniciar no tempo alocado. + Seu computador será reiniciado automaticamente no final da contagem regressiva. + Tempo restante: + Minimizar + Reinicie Agora + + + + + Você não tem espaço em disco suficiente para concluir a instalação de: + {0} + + Espaço necessário: {1}MB + Espaço disponível: {2}MB + + Libere espaço em disco suficiente para prosseguir com a instalação. + Continuar + Apenas selecione "Continuar" depois de fechar aplicativo(s) acima. + Fechar Programas + Adiar + Os seguintes programas precisam ser fechados antes que a instalação possa prosseguir. + + Salve seu trabalho, feche os programas e depois continue. + Como alternativa, salve seu trabalho e clique em "Fechar Programas". + OBSERVAÇÃO: O(s) programa(s) será(ão) automaticamente fechado(s) em: + O seguinte aplicativo está prestes a ser instalado: + Você pode optar por adiar a instalação até que o adiamento expire: + Depois que o adiamento expirar, você não terá mais a opção de adiar. + Adiamentos Restantes: + Prazo: + + The {0} will automatically continue in: + + + Instalação + Desinstalação + Reparação + iniciada. + concluída. + concluída. É necessário reiniciar. + falhou. + não concluída. + Instalação em andamento. Aguarde... + Desinstalação em andamento. Aguarde... + Reparação em andamento. Aguarde... + A execução deste aplicativo foi temporariamente bloqueada para que uma operação de instalação seja concluída. + Reinicialização Necessária + Para que a instalação seja concluída, é necessário reiniciar o computador. + Salve seu trabalho e reinicie dentro do prazo estipulado. + Seu computador será reiniciado automaticamente no final da contagem regressiva. + Tempo restante: + Minimizar + Reiniciar Agora + + + + + Usted no tiene suficiente espacio en disco para completar la instalación de: + {0} + + Espacio requerido: {1}MB + Espacio disponible: {2}MB + + Por favor, espacio en disco libre suficiente para proceder con la instalación. + Continuar + Seleccione "Continuar" después de cerrar la(s) aplicacion(es) de la lista arriba. + Cerrar Programas + Aplazar + Los siguientes programas deben cerrarse antes de la instalación puede proceder. + + Por favor, guarde el trabajo, cerrar los programas y luego continuar. + Alternativamente, guarde su trabajo y haga clic en "Cerrar Programas". + NOTA: El programa se cerrará automáticamente en: + La siguiente aplicación está a punto de instalarse: + Puede decidir aplazar la instalación hasta que expire el aplazamiento: + Una vez vencido el aplazamiento, ya no tendrá la opción de aplazar. + Restante aplazamientos: + Plazo: + + The {0} will automatically continue in: + + + Instalación + Desinstalación + Reparación + comenzó a. + completa. + completa. Se requiere un reinicio. + failed. + no. + Instalación en curso. Por favor, espere... + Desinstalación en curso. Por favor, espere... + Reparación en curso. Por favor, espere... + Lanzar esta aplicación ha sido bloqueado temporalmente para que pueda completar una operación de instalación. + Reiniciar Requerido + En orden para completar la instalación, debe reiniciar su computadora. + Por favor guarde su trabajo y reinicie dentro del tiempo asignado. + El ordenador se reiniciará automáticamente al final de la cuenta regresiva de. + Tiempo restante: + Minimizar + Reiniciar Ahora + + + + + Du har inte tillräckligt med ledigt diskutrymme för att kunna installera: + {0} + + Diskutrymme som krävs: {1}MB + Ledigt diskutrymme: {2}MB + + Frigör utrymme på hårddisken och försök igen. + Fortsätt + Välj "Fortsätt" endast efter att ha stängt applikation(er) i ovanstående lista. + Stäng Program + Skjut upp + Följande program måste stängas innan installationen kan fortsätta. + + Se till att spara ditt arbete, stäng de öppna programmen och klicka sen på "Fortsätt". + Alternativt, spara ditt arbete och klicka på "Stäng Program". + OBS: Programmen kommer automatiskt att avslutas om: + Följande applikation kommer att installeras: + Du kan välja att fördröja installationen ett begränsat antal gånger under en begränsad tid: + När antalet fördröjningar är slut eller deadlinen inträffar är detta alternativ inte längre tillgängligt. + Antal återstående fördröjningar: + Deadline: + + The {0} will automatically continue in: + + + Installation + Avinstallation + Reparation + startad. + slutförd. + slutförd. En omstart av datorn är nödvändig. + misslyckades. + ej slutförd. + Installation pågår. Var god vänta... + Avinstallation pågår. Var god vänta... + Reparation pågår. Var god vänta... + Den här applikationen har temporärt blockerats så att installationen kan slutföras. + Omstart Krävs + För att installationen ska kunna slutföras måste din dator startas om. + Se till att spara ditt arbete innan tiden går ut och en automatisk omstart sker. + Din dator kommer automatiskt att starta om när nedräkningen är slut. + Återstående tid: + Minimera + Starta Om Nu + + + + + ليس لديك مساحة كافية في القرص لإتمام عملية تثبيت: + {0} + + المساحة اللازمة: {1} م ب + المساحة المتاحة: {2} م ب + + يرجى تحرير مساحة قرص كافية كي تتم متابعة عملية التثبيت. + متابعة + قم باختيار "متابعة" فقط بعد إغلاق التطبيق/التطبيقات المدرجة أعلاه. + إغلاق البرامج + تأجيل + يجب إغلاق البرامج التالية قبل التمكن من متابعة عملية التثبيت. + + يرجى حفظ عملك، وإغلاق البرامج، ومن ثم المتابعة. + يمكنك بدلا من ذلك، حفظ عملك والنقر فوق "إغلاق البرامج". + ملاحظة: سيتم إغلاق البرنامج/البرامج بشكل تلقائي خلال: + التطبيق التالي على وشك التثبيت: + بإمكانك اختيار تأجيل التثبيت إلى حين انتهاء صلاحية التأجيل: + بمجرد انتهاء صلاحية التأجيل، لن يكون لديك خيار التأجيل بعد الآن. + التأجيلات المتبقية: + الموعد النهائي: + + The {0} will automatically continue in: + + + تثبيت + إزالة التثبيت + إصلاح + بدأ. + تم. + تم. يجب إعادة تشغيل النظام. + فشل. + غير مكتمل. + جاري التثبيت. يرجى الانتظار... + جارٍ إزالة التثبيت. يرجى الانتظار... + جارٍ إصلاح. يرجى الانتظار... + تم تعطيل تشغيل هذا التطبيق مؤقتًا بحيث يمكن إكمال عملية التثبيت. + مطلوب إعادة التشغيل + كي تكتمل عملية التثبيت، يجب عليك إعادة تشغيل حاسوبك. + يرجى حفظ عملك وإعادة التشغيل خلال الوقت المخصص. + ستتم إعادة تشغيل حاسوبك بشكل تلقائي عند نهاية العد التنازلي. + الزمن المتبقي: + تقليل + إعادة التشغيل الآن + + + + + :אין לך מספיק מקום בכונן כדי להשלים את ההתקנה של: + {0} + + מקום נדרש: {1}מ"ב + מקום זמין: {2}מ"ב + + אנא שחרר מספיק מקום בכונן כדי להתחיל בהתקנה. + המשך + בחר "המשך" רק לאחר שסגרת את היישום(ים) הרשומים לעיל. + סגור תכניות + דחה + יש לסגור את התכנות הבאות בטרם ההתקנה תוכל להתחיל. + + אנא שמור על העבודה שלך, סגור את התכניות, ואז המשך. + לחילופין, שמור על העבודה שלך והקלק על "סגור תכניות". + שים לב: התכנית(ות) תסגרנה באופן אוטומטי תוך: + היישום הבא עומד להיות מותקן: + אתה יכול לבחור לדחות את ההתקנה עד שמשך זמן הדחיה יפוג. + לאחר שמשך זמן הדחיה יפוג, לא תהיה לך עוד אפשרות לדחות. + מספר הדחיות שנותרו: + תאריך יעד: + + The {0} will automatically continue in: + + + התקנה + הסרה + תיקון + התחילה. + הושלמה. + הושלמה. נדרש אתחול המחשב. + נכשלה. + לא הושלמה. + מבצע התקנה. נא להמתין. + מבצע הסרה. נא להמתין. + מבצע תיקון. נא להמתין. + הרצת היישום הזה נחסמה זמנית כדי שפעולת התקנה תוכל להסתיים. + נדרש אתחול המחשב + כדי להשלים את ההתקנה, עליך לאתחל את המחשב שלך מחדש. + אנא שמור על העבודה שלך ואתחל במסגרת הזמן המוקצב. + המחשב שלך יאותחל באופן אוטומטי בסיום הספירה לאחור. + הזמן הנותר: + מזער את + אתחל עכשיו + + + + + 다음의 설치 완료를 위해 필요한 디스크 공간이 충분하지 않습니다: + {0} + + 필요한 공간: {1}MB + 사용 가능한 공간: {2}MB + + 설치를 계속하려면 디스크 공간을 충분하게 확보하세요. + 계속 + 위에 표시된 응용 프로그램을 종료한 후에만 "계속"을 선택하세요. + 프로그램 종료 + 연기 + 설치를 계속하려면 다음의 프로그램을 종료해야 합니다. + + 사용자 작업을 저장하고 프로그램을 종료한 후 계속하세요. + 다른 방법으로는 사용자 작업을 저장하고 "프로그램 종료"를 클릭하세요. + 참고: 프로그램이 자동으로 종료되는 경우: + 다음의 응용 프로그램을 설치합니다: + 지연 기간이 만료될 때까지 설치를 연기할 수 있습니다: + 일단 지연 기간이 만료되면 더 이상 연기할 수 있는 옵션은 없습니다. + 남은 지연 기간: + 마감: + + The {0} will automatically continue in: + + + 설치 + 제거 + 수리 + 시작되었습니다. + 완료되었습니다. + 완료되었습니다. 재부팅이 필요합니다. + 실패했습니다. + 완료되지 않았습니다. + 설치 중입니다. 기다리세요... + 제거 중입니다. 기다리세요... + 수리 중입니다. 기다리세요... + 설치 작업을 완료할 수 있도록 응용 프로그램의 시작을 잠시 차단했습니다. + 다시 시작해야 합니다 + 설치를 완료하려면 컴퓨터를 다시 시작해야 합니다. + 사용자 작업을 저장하고 지정된 시간 이내에 다시 시작하세요. + 카운트다운이 종료되면 컴퓨터는 자동으로 다시 시작합니다. + 남은 시간: + 최소화 + 지금 다시 시작 + + + + + У вас недостаточно пространства на диске для выполнения установки: + {0} + + Необходимое пространство на диске: {1}МБ + Доступное пространство на диске: {2}МБ + + Для продолжения установки, пожалуйста, освободите достаточно пространства на диске. + Продолжить + Выберите "Продолжить" только после закрытия вышеперечисленных приложений. + Закрыть программы + Отложить + Перед продолжением установки необходимо закрыть следующие программы. + + Пожалуйста, сохраните вашу работу и закройте программы, а затем продолжите установку. + Также вы можете сохранить вашу работу и нажать "Закрыть программы". + ПРИМЕЧАНИЕ: Эти программы будут автоматически закрыты через: + Планируется установка следующего приложения: + Вы можете отложить установку приложения до тех пор, пока не истечет срок действия этой отсрочки: + После истечения срока действия отсрочки вы больше не сможете отложить установку. + Оставшиеся отсрочки: + Дата истечения: + + The {0} will automatically continue in: + + + Установка + Удаление + ремонт + начата. + выполнена. + выполнена. Требуется перезагрузка. + не выполнена. + не завершена. + Идет установка. Пожалуйста, подождите... + Идет удаление. Пожалуйста, подождите... + Идет ремонт. Пожалуйста, подождите... + Запуск этого приложения временно заблокирован для завершения процесса установки. + Требуется перезагрузка + Для завершения установки необходимо перезагрузить ваш компьютер. + Пожалуйста, сохраните вашу работу и выполните перезагрузку в отведенное время. + Ваш компьютер будет автоматически перезагружен по завершению обратного отсчета. + Оставшееся время: + Минимизировать + Перезагрузить сейчас + + + + + 没有足够的磁盘空间来完成下列安装: + {0} + + 所需空间:{1}MB + 可用空间:{2}MB + + 请释放足够的磁盘空间以继续安装。 + 继续 + 在关闭上列应用程式后才选择"继续"。 + 关闭程序 + 延迟 + 为继续安装,必须关闭下列程序。 + + 请保存您的工作,关闭程序,然后继续。 + 或者保存您的工作,点击"关闭程序"。 + 注:下列程序将自动关闭: + 即将安装下列应用程式: + 在延期失效前,可选择延迟安装: + 延期失效后,再也无法延迟安装。 + 所剩延期: + 最后期限: + + The {0} will automatically continue in: + + + 安装 + 卸载 + 修复 + 已启动。 + 完成。 + 完成。必须重新启动。 + 失败。 + 未完成。 + 安装中。请稍等。。。 + 卸载中。请稍等。。。 + 修复中。请稍等。。。 + 为完成安装过程,暂时禁止启动这款应用程式。 + 需重启 + 为完成安装过程,需重启计算机。 + 请保存您的工作,并在容许时间重启计算机。 + 倒计时结束后,计算机将自动重启。 + 剩余时间: + 最小化 + 现在重启 + + + + + 沒有足夠的磁盤空間來完成下列安裝: + {0} + + 所需空間: {1}MB + 可用空間: {2}MB + + 請釋放足夠的磁盤空間以繼續安裝。 + 繼續 + 關閉上列應用程式後才選擇"繼續"。 + 關閉程序 + 延遲 + 在繼續安裝前必須關閉下列程序。 + + 請保存您的工作,關閉程序,然後繼續。 + 或者保存您的工作,然後點擊"關閉程序"。 + 注:下列程序將自動關閉: + 即將安裝下列應用程式: + 在延期失效前,可選擇延遲安裝: + 延期失效後,再也無法延遲安裝。 + 所剩延期: + 最後期限: + + The {0} will automatically continue in: + + + 安裝 + 卸載 + 修復 + 已啟動。 + 完成。 + 完成。需重啟。 + 失敗。 + 未完成。 + 安裝中。請稍等。。。 + 卸載中。請稍等。。。 + 修復中。請稍等。。。 + 為完成安裝過程,暫時禁止啟動本款應用程式。 + 需重啟 + 未完成安裝過程,需重啟計算機。 + 請保存您的工作,然後在容許時間重啟計算機。 + 倒計時結束後,計算機將自動重啟。 + 剩餘時間: + 最小化 + 現在重啟 + + + + + Nemáte dostatok voľného miesta na dokončenie inštalácie: + {0} + + Potrebné miesto: {1}MB + Voľné miesto: {2}MB + + Prosím, uvoľnite dostatok miesta pre pokračovanie inštalácie. + Pokračovať + Kliknite na "Pokračovať", keď zavriete vyššie uvedené aplikácie. + Ukončiť programy + Oddialiť + Nasledujúce programy musia byť zatvorené, než bude inštalácia pokračovať. + + Prosím, uložte svoju prácu, zatvorte dané programy a potom kliknite na pokračovať. + Prípadne môžete uložiť svoju prácu a potom kliknite na tlačidlo "Ukončiť programy". + Poznámka: Programy budú automaticky ukončené za: + Nasledujúca aplikácia bude nainštalovaná: + Inštaláciu môžete niekoľkokrát odložiť: + Akonáhle odklady uplynú, už nebudete mať možnosť odložiť inštaláciu. + Zostávajúce odklady: + Termín: + + {0} bude automaticky pokračovať za: + + + Inštalácia + Odinštalácia + Oprava + spustená. + ukončená. + ukončená. Je nutný reštart. + sa nepodarila. + nedokončená. + Inštalácia sa vykonáva. Prosím čakajte... + Prebieha odinštalácia. Prosím čakajte... + Vykonáva sa oprava. Prosím čakajte... + Spustenie tejto aplikácie bolo dočasne zablokované, aby mohla byť inštalácia dokončená úspešne. + Je nutný reštart. + Na dokončenie inštalácie musíte váš počítač reštartovať. + Prosím, uložte si prácu a reštartujte počítač v stanovenej lehote. + Na konci odpočítavania, bude váš počítač automaticky reštartovaný. + Zostávajúci čas: + Minimalizovať + Reštartovať Teraz + + + + + Nemáte dostatek volného místa na instalaci aplikace: + {0} + + Potřebné místo na disku: {1}MB + Dostupné místo na disku: {2}MB + + Uvolněte prosím dostatek místa k pokračovaní instalace. + Pokračovat + Klikněte na "Pokračovat", až budete mít výše uvedené aplikace zavřené. + Ukončit programy + Odložit + Následující programy musí být zavřené, aby instalace mohla pokračovat. + + Prosím, uložte svou práci, zavřete program a potom klikněte na "Pokračovat". + Případně můžete svou práci uložit a kliknout na tlačítko "Ukončit programy". + Upozornění: Programy budou automaticky zavřené za: + Nasledující aplikace bude nainstalována: + Instalaci můžete několikrát odložit: + Jakmile vyčerpáte všechna odložení, už nebudete mít šanci odložit instalaci. + Zbývající počet odložení: + Termín: + + {0} bude automaticky pokračovat za: + + + Instalace + Odinstalace + Oprava + zahájena. + dokončena. + dokončena. Je nutné restartovat počítač. + se nepodařila. + nedokončena. + Instalace právě probíhá. Prosím čekejte... + Probíhá odinstalace. Prosím čekejte... + Oprava právě probíhá. Prosím čekejte... + Spuštění této aplikace bylo dočasně zakázáno, aby mohla proběhnout instalace. + Je nutné restartovat počítač. + Pro dokončení instalace musíte váš počítač restartovat. + Prosím, uložte si práci a restartujte počítač ve stanoveném čase. + Na konci odpočítávání, bude váš počítač automaticky restartovaný. + Zbývající čas: + Minimalizovat + Restartovat nyní + + + + + Nincs elég lemezterület a telepítés végrehajtásához: + {0} + Szükséges lemezterület: {1}MB + Szabad lemezterület: {2}MB + Kérem szabadítson fel elegendő lemezterületet a telepítés végrehajtásához. + Tovább + Csak azután kattintson a „Tovább”-ra, ha a fentebb látható alkalmazás(oka)t bezárta. + Alkalmazások bezárása + Elhalaszt + Az alábbi programokat szíveskedjen bezárni, mielőtt a telepítés elkezdődik. + + Kérjük mentse munkáját és a folytatáshoz zárja be a futó alkalmazásokat. + Vagy + Kérjük mentse munkáját és kattintson a „Programok bezárása”-ra. + Megjegyzés: a programok automatikusan bezárásra kerülnek,: + A következő alkalmazások telepítésre kerülnek: + A telepítést elhalaszthatja amíg a rendelkezésre álló idő lejár: + Amennyiben a rendelkezésre álló idő letelik, nem lesz lehetősége a telepítés elhalasztására. + Fennmaradó halasztás: + Időpont: + + A(z) {0} automatikusan folytatódik: + + + Telepítés + Eltávolítás + Javítás + elindult. + elkészült. + elkészült.Újraindítás szükséges. + sikertelen. + nem lehet befejezni. + Telepítés folyamatban. Kérem várjon... + Eltávolítás folyamatban. Kérem várjon... + Javítás folyamatban. Kérem várjon... + A következő alkalmazások blokkolva lesznek, annak érdekében hogy a telepítés problémamentesen végrehajtódjon. + Újraindítás szükséges + A telepítés befejezéséhez a számítógépet újraindítása szükséges. + Kérem mentse munkáját, és a megadott időn belül indítsa újra.. + A hátralévő idő leteltével a számítógép újraindul. + Hátralévő idő: + Minimalizál + Újraindítás most + + + diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitExtensions.ps1 b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitExtensions.ps1 new file mode 100644 index 0000000..668b74d --- /dev/null +++ b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitExtensions.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + This script is a template that allows you to extend the toolkit with your own custom functions. + # LICENSE # + PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. + Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . +.DESCRIPTION + The script is automatically dot-sourced by the AppDeployToolkitMain.ps1 script. +.NOTES + Toolkit Exit Code Ranges: + 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 + 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 + 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 +.LINK + http://psappdeploytoolkit.com +#> +[CmdletBinding()] +Param ( +) + +##*=============================================== +##* VARIABLE DECLARATION +##*=============================================== + +# Variables: Script +[string]$appDeployToolkitExtName = 'PSAppDeployToolkitExt' +[string]$appDeployExtScriptFriendlyName = 'App Deploy Toolkit Extensions' +[version]$appDeployExtScriptVersion = [version]'3.8.3' +[string]$appDeployExtScriptDate = '30/09/2020' +[hashtable]$appDeployExtScriptParameters = $PSBoundParameters + +##*=============================================== +##* FUNCTION LISTINGS +##*=============================================== + +# + +##*=============================================== +##* END FUNCTION LISTINGS +##*=============================================== + +##*=============================================== +##* SCRIPT BODY +##*=============================================== + +If ($scriptParentPath) { + Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] dot-source invoked by [$(((Get-Variable -Name MyInvocation).Value).ScriptName)]" -Source $appDeployToolkitExtName +} Else { + Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] invoked directly" -Source $appDeployToolkitExtName +} + +##*=============================================== +##* END SCRIPT BODY +##*=============================================== diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitHelp.ps1 b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitHelp.ps1 new file mode 100644 index 0000000..2d796e0 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitHelp.ps1 @@ -0,0 +1,130 @@ +<# +.SYNOPSIS + Displays a graphical console to browse the help for the App Deployment Toolkit functions + # LICENSE # + PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. + Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . +.DESCRIPTION + Displays a graphical console to browse the help for the App Deployment Toolkit functions +.EXAMPLE + AppDeployToolkitHelp.ps1 +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + +##*=============================================== +##* VARIABLE DECLARATION +##*=============================================== + +## Variables: Script +[string]$appDeployToolkitHelpName = 'PSAppDeployToolkitHelp' +[string]$appDeployHelpScriptFriendlyName = 'App Deploy Toolkit Help' +[version]$appDeployHelpScriptVersion = [version]'3.8.3' +[string]$appDeployHelpScriptDate = '30/09/2020' + +## Variables: Environment +[string]$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +# Dot source the App Deploy Toolkit Functions +. "$scriptDirectory\AppDeployToolkitMain.ps1" -DisableLogging +. "$scriptDirectory\AppDeployToolkitExtensions.ps1" +##*=============================================== +##* END VARIABLE DECLARATION +##*=============================================== + +##*=============================================== +##* FUNCTION LISTINGS +##*=============================================== + +Function Show-HelpConsole { + ## Import the Assemblies + Add-Type -AssemblyName 'System.Windows.Forms' -ErrorAction 'Stop' + Add-Type -AssemblyName System.Drawing -ErrorAction 'Stop' + + ## Form Objects + $HelpForm = New-Object -TypeName 'System.Windows.Forms.Form' + $HelpListBox = New-Object -TypeName 'System.Windows.Forms.ListBox' + $HelpTextBox = New-Object -TypeName 'System.Windows.Forms.RichTextBox' + $InitialFormWindowState = New-Object -TypeName 'System.Windows.Forms.FormWindowState' + + ## Form Code + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' + $System_Drawing_Size.Height = 665 + $System_Drawing_Size.Width = 957 + $HelpForm.ClientSize = $System_Drawing_Size + $HelpForm.DataBindings.DefaultDataSourceUpdateMode = 0 + $HelpForm.Name = 'HelpForm' + $HelpForm.Text = 'PowerShell App Deployment Toolkit Help Console' + $HelpForm.WindowState = 'Normal' + $HelpForm.ShowInTaskbar = $true + $HelpForm.FormBorderStyle = 'Fixed3D' + $HelpForm.MaximizeBox = $false + $HelpForm.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + $HelpListBox.Anchor = 7 + $HelpListBox.BorderStyle = 1 + $HelpListBox.DataBindings.DefaultDataSourceUpdateMode = 0 + $HelpListBox.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ('Microsoft Sans Serif', 9.75, 1, 3, 1) + $HelpListBox.FormattingEnabled = $true + $HelpListBox.ItemHeight = 16 + $System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' + $System_Drawing_Point.X = 0 + $System_Drawing_Point.Y = 0 + $HelpListBox.Location = $System_Drawing_Point + $HelpListBox.Name = 'HelpListBox' + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' + $System_Drawing_Size.Height = 658 + $System_Drawing_Size.Width = 271 + $HelpListBox.Size = $System_Drawing_Size + $HelpListBox.Sorted = $true + $HelpListBox.TabIndex = 2 + $HelpListBox.add_SelectedIndexChanged({ $HelpTextBox.Text = Get-Help -Name $HelpListBox.SelectedItem -Full | Out-String }) + $helpFunctions = Get-Command -CommandType 'Function' | Where-Object { ($_.HelpUri -match 'psappdeploytoolkit') -and ($_.Definition -notmatch 'internal script function') } | Select-Object -ExpandProperty Name + $null = $HelpListBox.Items.AddRange($helpFunctions) + $HelpForm.Controls.Add($HelpListBox) + $HelpTextBox.Anchor = 11 + $HelpTextBox.BorderStyle = 1 + $HelpTextBox.DataBindings.DefaultDataSourceUpdateMode = 0 + $HelpTextBox.Font = New-Object -TypeName 'System.Drawing.Font' -ArgumentList ('Microsoft Sans Serif', 8.5, 0, 3, 1) + $HelpTextBox.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) + $System_Drawing_Point = New-Object -TypeName System.Drawing.Point + $System_Drawing_Point.X = 277 + $System_Drawing_Point.Y = 0 + $HelpTextBox.Location = $System_Drawing_Point + $HelpTextBox.Name = 'HelpTextBox' + $HelpTextBox.ReadOnly = $True + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' + $System_Drawing_Size.Height = 658 + $System_Drawing_Size.Width = 680 + $HelpTextBox.Size = $System_Drawing_Size + $HelpTextBox.TabIndex = 1 + $HelpTextBox.Text = '' + $HelpForm.Controls.Add($HelpTextBox) + + ## Save the initial state of the form + $InitialFormWindowState = $HelpForm.WindowState + ## Init the OnLoad event to correct the initial state of the form + $HelpForm.add_Load($OnLoadForm_StateCorrection) + ## Show the Form + $null = $HelpForm.ShowDialog() +} + +##*=============================================== +##* END FUNCTION LISTINGS +##*=============================================== + +##*=============================================== +##* SCRIPT BODY +##*=============================================== + +Write-Log -Message "Load [$appDeployHelpScriptFriendlyName] console..." -Source $appDeployToolkitHelpName + +## Show the help console +Show-HelpConsole + +Write-Log -Message "[$appDeployHelpScriptFriendlyName] console closed." -Source $appDeployToolkitHelpName + +##*=============================================== +##* END SCRIPT BODY +##*=============================================== diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitLogo.ico b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitLogo.ico new file mode 100644 index 0000000..21eed72 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitLogo.ico differ diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.cs b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.cs new file mode 100644 index 0000000..7d3f200 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.cs @@ -0,0 +1,792 @@ +// Date Modified: 30/09/2020 +// Version Number: 3.8.3 + +using System; +using System.Text; +using System.Collections; +using System.ComponentModel; +using System.DirectoryServices; +using System.Security.Principal; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + +namespace PSADT +{ + public class Msi + { + enum LoadLibraryFlags : int + { + DONT_RESOLVE_DLL_REFERENCES = 0x00000001, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, + LOAD_LIBRARY_AS_DATAFILE = 0x00000002, + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, + LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 + } + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, LoadLibraryFlags dwFlags); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + static extern int LoadString(IntPtr hInstance, int uID, StringBuilder lpBuffer, int nBufferMax); + + // Get MSI exit code message from msimsg.dll resource dll + public static string GetMessageFromMsiExitCode(int errCode) + { + IntPtr hModuleInstance = LoadLibraryEx("msimsg.dll", IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE); + + StringBuilder sb = new StringBuilder(255); + LoadString(hModuleInstance, errCode, sb, sb.Capacity + 1); + + return sb.ToString(); + } + } + + public class Explorer + { + private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff); + private const int WM_SETTINGCHANGE = 0x1a; + private const int SMTO_ABORTIFHUNG = 0x0002; + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + static extern bool SendNotifyMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, IntPtr lpdwResult); + + [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); + + public static void RefreshDesktopAndEnvironmentVariables() + { + // Update desktop icons + SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero); + SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero); + // Update environment variables + SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero); + } + } + + public sealed class FileVerb + { + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int LoadString(IntPtr h, int id, StringBuilder sb, int maxBuffer); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr LoadLibrary(string s); + + public static string GetPinVerb(int VerbId) + { + IntPtr hShell32 = LoadLibrary("shell32.dll"); + const int nChars = 255; + StringBuilder Buff = new StringBuilder("", nChars); + + LoadString(hShell32, VerbId, Buff, Buff.Capacity); + return Buff.ToString(); + } + } + + public sealed class IniFile + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool WritePrivateProfileString(string lpAppName, string lpKeyName, StringBuilder lpString, string lpFileName); + + public static string GetIniValue(string section, string key, string filepath) + { + string sDefault = ""; + const int nChars = 1024; + StringBuilder Buff = new StringBuilder(nChars); + + GetPrivateProfileString(section, key, sDefault, Buff, Buff.Capacity, filepath); + return Buff.ToString(); + } + + public static void SetIniValue(string section, string key, StringBuilder value, string filepath) + { + WritePrivateProfileString(section, key, value, filepath); + } + } + + public class UiAutomation + { + public enum GetWindow_Cmd : int + { + GW_HWNDFIRST = 0, + GW_HWNDLAST = 1, + GW_HWNDNEXT = 2, + GW_HWNDPREV = 3, + GW_OWNER = 4, + GW_CHILD = 5, + GW_ENABLEDPOPUP = 6 + } + + public enum ShowWindowEnum + { + Hide = 0, + ShowNormal = 1, + ShowMinimized = 2, + ShowMaximized = 3, + Maximize = 3, + ShowNormalNoActivate = 4, + Show = 5, + Minimize = 6, + ShowMinNoActivate = 7, + ShowNoActivate = 8, + Restore = 9, + ShowDefault = 10, + ForceMinimized = 11 + } + + public enum UserNotificationState + { + // http://msdn.microsoft.com/en-us/library/bb762533(v=vs.85).aspx + ScreenSaverOrLockedOrFastUserSwitching =1, // A screen saver is displayed, the machine is locked, or a nonactive Fast User Switching session is in progress. + FullScreenOrPresentationModeOrLoginScreen =2, // A full-screen application is running or Presentation Settings are applied. Presentation Settings allow a user to put their machine into a state fit for an uninterrupted presentation, such as a set of PowerPoint slides, with a single click. Also returns this state if machine is at the login screen. + RunningDirect3DFullScreen =3, // A full-screen, exclusive mode, Direct3D application is running. + PresentationMode =4, // The user has activated Windows presentation settings to block notifications and pop-up messages. + AcceptsNotifications =5, // None of the other states are found, notifications can be freely sent. + QuietTime =6, // Introduced in Windows 7. The current user is in "quiet time", which is the first hour after a new user logs into his or her account for the first time. + WindowsStoreAppRunning =7 // Introduced in Windows 8. A Windows Store app is running. + } + + // Only for Vista or above + [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] + static extern int SHQueryUserNotificationState(out UserNotificationState pquns); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumWindows(EnumWindowsProcD lpEnumFunc, ref IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + private static extern IntPtr GetShellWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsWindowEnabled(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr SetActiveWindow(IntPtr hwnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr SetFocus(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool BringWindowToTop(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetCurrentThreadId(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach); + + [DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr DestroyMenu(IntPtr hWnd); + + public delegate bool EnumWindowsProcD(IntPtr hWnd, ref IntPtr lItems); + + public static bool EnumWindowsProc(IntPtr hWnd, ref IntPtr lItems) + { + if (hWnd != IntPtr.Zero) + { + GCHandle hItems = GCHandle.FromIntPtr(lItems); + List items = hItems.Target as List; + items.Add(hWnd); + return true; + } + else + { + return false; + } + } + + public static List EnumWindows() + { + try + { + List items = new List(); + EnumWindowsProcD CallBackPtr = new EnumWindowsProcD(EnumWindowsProc); + GCHandle hItems = GCHandle.Alloc(items); + IntPtr lItems = GCHandle.ToIntPtr(hItems); + EnumWindows(CallBackPtr, ref lItems); + return items; + } + catch (Exception ex) + { + throw new Exception("An error occured during window enumeration: " + ex.Message); + } + } + + public static string GetWindowText(IntPtr hWnd) + { + int iTextLength = GetWindowTextLength(hWnd); + if (iTextLength > 0) + { + StringBuilder sb = new StringBuilder(iTextLength); + GetWindowText(hWnd, sb, iTextLength + 1); + return sb.ToString(); + } + else + { + return String.Empty; + } + } + + public static bool BringWindowToFront(IntPtr windowHandle) + { + bool breturn = false; + if (IsIconic(windowHandle)) + { + // Show minimized window because SetForegroundWindow does not work for minimized windows + ShowWindow(windowHandle, ShowWindowEnum.ShowMaximized); + } + + int lpdwProcessId; + int windowThreadProcessId = GetWindowThreadProcessId(GetForegroundWindow(), out lpdwProcessId); + int currentThreadId = GetCurrentThreadId(); + AttachThreadInput(windowThreadProcessId, currentThreadId, true); + + BringWindowToTop(windowHandle); + breturn = SetForegroundWindow(windowHandle); + SetActiveWindow(windowHandle); + SetFocus(windowHandle); + + AttachThreadInput(windowThreadProcessId, currentThreadId, false); + return breturn; + } + + public static int GetWindowThreadProcessId(IntPtr windowHandle) + { + int processID = 0; + GetWindowThreadProcessId(windowHandle, out processID); + return processID; + } + + public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex) + { + if (IntPtr.Size == 4) + { + return GetWindowLong32(hWnd, nIndex); + } + return GetWindowLongPtr64(hWnd, nIndex); + } + + public static string GetUserNotificationState() + { + // Only works for Windows Vista or higher + UserNotificationState state; + int returnVal = SHQueryUserNotificationState(out state); + return state.ToString(); + } + } + + public class QueryUser + { + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr WTSOpenServer(string pServerName); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern void WTSCloseServer(IntPtr hServer); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] + public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr pBuffer, out int pBytesReturned); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] + public static extern int WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr pSessionInfo, out int pCount); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("winsta.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int WinStationQueryInformation(IntPtr hServer, int sessionId, int information, ref WINSTATIONINFORMATIONW pBuffer, int bufferLength, ref int returnedLength); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetCurrentProcessId(); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool ProcessIdToSessionId(int processId, ref int pSessionId); + + public class TerminalSessionData + { + public int SessionId; + public string ConnectionState; + public string SessionName; + public bool IsUserSession; + public TerminalSessionData(int sessionId, string connState, string sessionName, bool isUserSession) + { + SessionId = sessionId; + ConnectionState = connState; + SessionName = sessionName; + IsUserSession = isUserSession; + } + } + + public class TerminalSessionInfo + { + public string NTAccount; + public string SID; + public string UserName; + public string DomainName; + public int SessionId; + public string SessionName; + public string ConnectState; + public bool IsCurrentSession; + public bool IsConsoleSession; + public bool IsActiveUserSession; + public bool IsUserSession; + public bool IsRdpSession; + public bool IsLocalAdmin; + public DateTime? LogonTime; + public TimeSpan? IdleTime; + public DateTime? DisconnectTime; + public string ClientName; + public string ClientProtocolType; + public string ClientDirectory; + public int ClientBuildNumber; + } + + [StructLayout(LayoutKind.Sequential)] + private struct WTS_SESSION_INFO + { + public Int32 SessionId; + [MarshalAs(UnmanagedType.LPStr)] + public string SessionName; + public WTS_CONNECTSTATE_CLASS State; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WINSTATIONINFORMATIONW + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 70)] + private byte[] Reserved1; + public int SessionId; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private byte[] Reserved2; + public FILETIME ConnectTime; + public FILETIME DisconnectTime; + public FILETIME LastInputTime; + public FILETIME LoginTime; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1096)] + private byte[] Reserved3; + public FILETIME CurrentTime; + } + + public enum WINSTATIONINFOCLASS + { + WinStationInformation = 8 + } + + public enum WTS_CONNECTSTATE_CLASS + { + Active, + Connected, + ConnectQuery, + Shadow, + Disconnected, + Idle, + Listen, + Reset, + Down, + Init + } + + public enum WTS_INFO_CLASS + { + SessionId=4, + UserName, + SessionName, + DomainName, + ConnectState, + ClientBuildNumber, + ClientName, + ClientDirectory, + ClientProtocolType=16 + } + + private static IntPtr OpenServer(string Name) + { + IntPtr server = WTSOpenServer(Name); + return server; + } + + private static void CloseServer(IntPtr ServerHandle) + { + WTSCloseServer(ServerHandle); + } + + private static IList PtrToStructureList(IntPtr ppList, int count) where T : struct + { + List result = new List(); + long pointer = ppList.ToInt64(); + int sizeOf = Marshal.SizeOf(typeof(T)); + + for (int index = 0; index < count; index++) + { + T item = (T) Marshal.PtrToStructure(new IntPtr(pointer), typeof(T)); + result.Add(item); + pointer += sizeOf; + } + return result; + } + + public static DateTime? FileTimeToDateTime(FILETIME ft) + { + if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0) + { + return null; + } + long hFT = (((long) ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + return DateTime.FromFileTime(hFT); + } + + public static WINSTATIONINFORMATIONW GetWinStationInformation(IntPtr server, int sessionId) + { + int retLen = 0; + WINSTATIONINFORMATIONW wsInfo = new WINSTATIONINFORMATIONW(); + WinStationQueryInformation(server, sessionId, (int) WINSTATIONINFOCLASS.WinStationInformation, ref wsInfo, Marshal.SizeOf(typeof(WINSTATIONINFORMATIONW)), ref retLen); + return wsInfo; + } + + public static TerminalSessionData[] ListSessions(string ServerName) + { + IntPtr server = IntPtr.Zero; + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + + List results = new List(); + + try + { + server = OpenServer(ServerName); + IntPtr ppSessionInfo = IntPtr.Zero; + int count; + bool _isUserSession = false; + IList sessionsInfo; + + if (WTSEnumerateSessions(server, 0, 1, out ppSessionInfo, out count) == 0) + { + throw new Win32Exception(); + } + + try + { + sessionsInfo = PtrToStructureList(ppSessionInfo, count); + } + finally + { + WTSFreeMemory(ppSessionInfo); + } + + foreach (WTS_SESSION_INFO sessionInfo in sessionsInfo) + { + if (sessionInfo.SessionName != "Services" && sessionInfo.SessionName != "RDP-Tcp") + { + _isUserSession = true; + } + results.Add(new TerminalSessionData(sessionInfo.SessionId, sessionInfo.State.ToString(), sessionInfo.SessionName, _isUserSession)); + _isUserSession = false; + } + } + finally + { + CloseServer(server); + } + + TerminalSessionData[] returnData = results.ToArray(); + return returnData; + } + + public static TerminalSessionInfo GetSessionInfo(string ServerName, int SessionId) + { + IntPtr server = IntPtr.Zero; + IntPtr buffer = IntPtr.Zero; + int bytesReturned; + TerminalSessionInfo data = new TerminalSessionInfo(); + bool _IsCurrentSessionId = false; + bool _IsConsoleSession = false; + bool _IsUserSession = false; + int currentSessionID = 0; + string _NTAccount = String.Empty; + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + if (ProcessIdToSessionId(GetCurrentProcessId(), ref currentSessionID) == false) + { + currentSessionID = -1; + } + + // Get all members of the local administrators group + bool _IsLocalAdminCheckSuccess = false; + List localAdminGroupSidsList = new List(); + try + { + DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + ServerName + ",Computer"); + string localAdminGroupName = new SecurityIdentifier("S-1-5-32-544").Translate(typeof(NTAccount)).Value.Split('\\')[1]; + DirectoryEntry admGroup = localMachine.Children.Find(localAdminGroupName, "group"); + object members = admGroup.Invoke("members", null); + string validSidPattern = @"^S-\d-\d+-(\d+-){1,14}\d+$"; + foreach (object groupMember in (IEnumerable)members) + { + DirectoryEntry member = new DirectoryEntry(groupMember); + if (member.Name != String.Empty) + { + if (Regex.IsMatch(member.Name, validSidPattern)) + { + localAdminGroupSidsList.Add(member.Name); + } + else + { + localAdminGroupSidsList.Add((new NTAccount(member.Name)).Translate(typeof(SecurityIdentifier)).Value); + } + } + } + _IsLocalAdminCheckSuccess = true; + } + catch { } + + try + { + server = OpenServer(ServerName); + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientBuildNumber, out buffer, out bytesReturned) == false) + { + return data; + } + int lData = Marshal.ReadInt32(buffer); + data.ClientBuildNumber = lData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientDirectory, out buffer, out bytesReturned) == false) + { + return data; + } + string strData = Marshal.PtrToStringAnsi(buffer); + data.ClientDirectory = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.ClientName = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientProtocolType, out buffer, out bytesReturned) == false) + { + return data; + } + Int16 intData = Marshal.ReadInt16(buffer); + if (intData == 2) + { + strData = "RDP"; + data.IsRdpSession = true; + } + else + { + strData = ""; + data.IsRdpSession = false; + } + data.ClientProtocolType = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ConnectState, out buffer, out bytesReturned) == false) + { + return data; + } + lData = Marshal.ReadInt32(buffer); + data.ConnectState = ((WTS_CONNECTSTATE_CLASS) lData).ToString(); + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionId, out buffer, out bytesReturned) == false) + { + return data; + } + lData = Marshal.ReadInt32(buffer); + data.SessionId = lData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.DomainName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer).ToUpper(); + data.DomainName = strData; + if (strData != String.Empty) + { + _NTAccount = strData; + } + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.UserName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.UserName = strData; + if (strData != String.Empty) + { + data.NTAccount = _NTAccount + "\\" + strData; + string _Sid = (new NTAccount(_NTAccount + "\\" + strData)).Translate(typeof(SecurityIdentifier)).Value; + data.SID = _Sid; + if (_IsLocalAdminCheckSuccess == true) + { + foreach (string localAdminGroupSid in localAdminGroupSidsList) + { + if (localAdminGroupSid == _Sid) + { + data.IsLocalAdmin = true; + break; + } + else + { + data.IsLocalAdmin = false; + } + } + } + } + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.SessionName = strData; + if (strData != "Services" && strData != "RDP-Tcp" && data.UserName != String.Empty) + { + _IsUserSession = true; + } + data.IsUserSession = _IsUserSession; + if (strData == "Console") + { + _IsConsoleSession = true; + } + data.IsConsoleSession = _IsConsoleSession; + + WINSTATIONINFORMATIONW wsInfo = GetWinStationInformation(server, SessionId); + DateTime? _loginTime = FileTimeToDateTime(wsInfo.LoginTime); + DateTime? _lastInputTime = FileTimeToDateTime(wsInfo.LastInputTime); + DateTime? _disconnectTime = FileTimeToDateTime(wsInfo.DisconnectTime); + DateTime? _currentTime = FileTimeToDateTime(wsInfo.CurrentTime); + TimeSpan? _idleTime = (_currentTime != null && _lastInputTime != null) ? _currentTime.Value - _lastInputTime.Value : TimeSpan.Zero; + data.LogonTime = _loginTime; + data.IdleTime = _idleTime; + data.DisconnectTime = _disconnectTime; + + if (currentSessionID == SessionId) + { + _IsCurrentSessionId = true; + } + data.IsCurrentSession = _IsCurrentSessionId; + } + finally + { + WTSFreeMemory(buffer); + buffer = IntPtr.Zero; + CloseServer(server); + } + return data; + } + + public static TerminalSessionInfo[] GetUserSessionInfo(string ServerName) + { + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + + // Find and get detailed information for all user sessions + // Also determine the active user session. If a console user exists, then that will be the active user session. + // If no console user exists but users are logged in, such as on terminal servers, then select the first logged-in non-console user that is either 'Active' or 'Connected' as the active user. + TerminalSessionData[] sessions = ListSessions(ServerName); + TerminalSessionInfo sessionInfo = new TerminalSessionInfo(); + List userSessionsInfo = new List(); + string firstActiveUserNTAccount = String.Empty; + bool IsActiveUserSessionSet = false; + foreach (TerminalSessionData session in sessions) + { + if (session.IsUserSession == true) + { + sessionInfo = GetSessionInfo(ServerName, session.SessionId); + if (sessionInfo.IsUserSession == true) + { + if ((firstActiveUserNTAccount == String.Empty) && (sessionInfo.ConnectState == "Active" || sessionInfo.ConnectState == "Connected")) + { + firstActiveUserNTAccount = sessionInfo.NTAccount; + } + + if (sessionInfo.IsConsoleSession == true) + { + sessionInfo.IsActiveUserSession = true; + IsActiveUserSessionSet = true; + } + else + { + sessionInfo.IsActiveUserSession = false; + } + + userSessionsInfo.Add(sessionInfo); + } + } + } + + TerminalSessionInfo[] userSessions = userSessionsInfo.ToArray(); + if (IsActiveUserSessionSet == false) + { + foreach (TerminalSessionInfo userSession in userSessions) + { + if (userSession.NTAccount == firstActiveUserNTAccount) + { + userSession.IsActiveUserSession = true; + break; + } + } + } + + return userSessions; + } + } +} diff --git a/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.ps1 b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.ps1 new file mode 100644 index 0000000..2979011 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/AppDeployToolkit/AppDeployToolkitMain.ps1 @@ -0,0 +1,11604 @@ +<# +.SYNOPSIS + This script contains the functions and logic engine for the Deploy-Application.ps1 script. + # LICENSE # + PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. + Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . +.DESCRIPTION + The script can be called directly to dot-source the toolkit functions for testing, but it is usually called by the Deploy-Application.ps1 script. + The script can usually be updated to the latest version without impacting your per-application Deploy-Application scripts. + Please check release notes before upgrading. +.PARAMETER CleanupBlockedApps + Clean up the blocked applications. + This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously. +.PARAMETER ShowBlockedAppDialog + Display a dialog box showing that the application execution is blocked. + This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously. +.PARAMETER ReferredInstallName + Name of the referring application that invoked the script externally. + This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously. +.PARAMETER ReferredInstallTitle + Title of the referring application that invoked the script externally. + This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously. +.PARAMETER ReferredLogname + Logfile name of the referring application that invoked the script externally. + This parameter is passed to the script when it is called externally, e.g. from a scheduled task or asynchronously. +.PARAMETER AsyncToolkitLaunch + This parameter is passed to the script when it is being called externally, e.g. from a scheduled task or asynchronously. +.NOTES + The other parameters specified for this script that are not documented in this help section are for use only by functions in this script that call themselves by running this script again asynchronously. +.LINK + http://psappdeploytoolkit.com +#> +[CmdletBinding()] +Param ( + ## Script Parameters: These parameters are passed to the script when it is called externally from a scheduled task or because of an Image File Execution Options registry setting + [switch]$ShowInstallationPrompt = $false, + [switch]$ShowInstallationRestartPrompt = $false, + [switch]$CleanupBlockedApps = $false, + [switch]$ShowBlockedAppDialog = $false, + [switch]$DisableLogging = $false, + [string]$ReferredInstallName = '', + [string]$ReferredInstallTitle = '', + [string]$ReferredLogName = '', + [string]$Title = '', + [string]$Message = '', + [string]$MessageAlignment = '', + [string]$ButtonRightText = '', + [string]$ButtonLeftText = '', + [string]$ButtonMiddleText = '', + [string]$Icon = '', + [string]$Timeout = '', + [switch]$ExitOnTimeout = $false, + [boolean]$MinimizeWindows = $false, + [switch]$PersistPrompt = $false, + [int32]$CountdownSeconds, + [int32]$CountdownNoHideSeconds, + [switch]$NoCountdown = $false, + [switch]$AsyncToolkitLaunch = $false +) + +##*============================================= +##* VARIABLE DECLARATION +##*============================================= +#region VariableDeclaration + +## Variables: Toolkit Name +[string]$appDeployToolkitName = 'PSAppDeployToolkit' +[string]$appDeployMainScriptFriendlyName = 'App Deploy Toolkit Main' + +## Variables: Script Info +[version]$appDeployMainScriptVersion = [version]'3.8.3' +[version]$appDeployMainScriptMinimumConfigVersion = [version]'3.8.3' +[string]$appDeployMainScriptDate = '30/09/2020' +[hashtable]$appDeployMainScriptParameters = $PSBoundParameters + +## Variables: Datetime and Culture +[datetime]$currentDateTime = Get-Date +[string]$currentTime = Get-Date -Date $currentDateTime -UFormat '%T' +[string]$currentDate = Get-Date -Date $currentDateTime -UFormat '%d-%m-%Y' +[timespan]$currentTimeZoneBias = [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now) +[Globalization.CultureInfo]$culture = Get-Culture +[string]$currentLanguage = $culture.TwoLetterISOLanguageName.ToUpper() +[Globalization.CultureInfo]$uiculture = Get-UICulture +[string]$currentUILanguage = $uiculture.TwoLetterISOLanguageName.ToUpper() + +## Variables: Environment Variables +[psobject]$envHost = $Host +[psobject]$envShellFolders = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -ErrorAction 'SilentlyContinue' +[string]$envAllUsersProfile = $env:ALLUSERSPROFILE +[string]$envAppData = [Environment]::GetFolderPath('ApplicationData') +[string]$envArchitecture = $env:PROCESSOR_ARCHITECTURE +[string]$envCommonDesktop = $envShellFolders | Select-Object -ExpandProperty 'Common Desktop' -ErrorAction 'SilentlyContinue' +[string]$envCommonDocuments = $envShellFolders | Select-Object -ExpandProperty 'Common Documents' -ErrorAction 'SilentlyContinue' +[string]$envCommonStartMenuPrograms = $envShellFolders | Select-Object -ExpandProperty 'Common Programs' -ErrorAction 'SilentlyContinue' +[string]$envCommonStartMenu = $envShellFolders | Select-Object -ExpandProperty 'Common Start Menu' -ErrorAction 'SilentlyContinue' +[string]$envCommonStartUp = $envShellFolders | Select-Object -ExpandProperty 'Common Startup' -ErrorAction 'SilentlyContinue' +[string]$envCommonTemplates = $envShellFolders | Select-Object -ExpandProperty 'Common Templates' -ErrorAction 'SilentlyContinue' +[string]$envComputerName = [Environment]::MachineName.ToUpper() +[string]$envHomeDrive = $env:HOMEDRIVE +[string]$envHomePath = $env:HOMEPATH +[string]$envHomeShare = $env:HOMESHARE +[string]$envLocalAppData = [Environment]::GetFolderPath('LocalApplicationData') +[string[]]$envLogicalDrives = [Environment]::GetLogicalDrives() +[string]$envProgramData = [Environment]::GetFolderPath('CommonApplicationData') +[string]$envPublic = $env:PUBLIC +[string]$envSystemDrive = $env:SYSTEMDRIVE +[string]$envSystemRoot = $env:SYSTEMROOT +[string]$envTemp = [IO.Path]::GetTempPath() +[string]$envUserCookies = [Environment]::GetFolderPath('Cookies') +[string]$envUserDesktop = [Environment]::GetFolderPath('DesktopDirectory') +[string]$envUserFavorites = [Environment]::GetFolderPath('Favorites') +[string]$envUserInternetCache = [Environment]::GetFolderPath('InternetCache') +[string]$envUserInternetHistory = [Environment]::GetFolderPath('History') +[string]$envUserMyDocuments = [Environment]::GetFolderPath('MyDocuments') +[string]$envUserName = [Environment]::UserName +[string]$envUserPictures = [Environment]::GetFolderPath('MyPictures') +[string]$envUserProfile = $env:USERPROFILE +[string]$envUserSendTo = [Environment]::GetFolderPath('SendTo') +[string]$envUserStartMenu = [Environment]::GetFolderPath('StartMenu') +[string]$envUserStartMenuPrograms = [Environment]::GetFolderPath('Programs') +[string]$envUserStartUp = [Environment]::GetFolderPath('StartUp') +[string]$envUserTemplates = [Environment]::GetFolderPath('Templates') +[string]$envSystem32Directory = [Environment]::SystemDirectory +[string]$envWinDir = $env:WINDIR + +## Variables: Domain Membership +[boolean]$IsMachinePartOfDomain = (Get-WmiObject -Class 'Win32_ComputerSystem' -ErrorAction 'SilentlyContinue').PartOfDomain +[string]$envMachineWorkgroup = '' +[string]$envMachineADDomain = '' +[string]$envLogonServer = '' +[string]$MachineDomainController = '' +[string]$envComputerNameFQDN = $envComputerName +If ($IsMachinePartOfDomain) { + [string]$envMachineADDomain = (Get-WmiObject -Class 'Win32_ComputerSystem' -ErrorAction 'SilentlyContinue').Domain | Where-Object { $_ } | ForEach-Object { $_.ToLower() } + try { + $envComputerNameFQDN = ([Net.Dns]::GetHostEntry('localhost')).HostName + } + catch { + # Function GetHostEntry failed, but we can construct the FQDN in another way + $envComputerNameFQDN = $envComputerNameFQDN + "." + $envMachineADDomain + } + + Try { + [string]$envLogonServer = $env:LOGONSERVER | Where-Object { (($_) -and (-not $_.Contains('\\MicrosoftAccount'))) } | ForEach-Object { $_.TrimStart('\') } | ForEach-Object { ([Net.Dns]::GetHostEntry($_)).HostName } + # If running in system context, fall back on the logonserver value stored in the registry + If (-not $envLogonServer) { [string]$envLogonServer = Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History' -ErrorAction 'SilentlyContinue' | Select-Object -ExpandProperty 'DCName' -ErrorAction 'SilentlyContinue' } + } + Catch { + # If GetHostEntry fails, just use the registry value + [string]$envLogonServer = Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History' -ErrorAction 'SilentlyContinue' | Select-Object -ExpandProperty 'DCName' -ErrorAction 'SilentlyContinue' + } + + try { + [string]$MachineDomainController = [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().FindDomainController().Name + } + catch { } +} +Else { + [string]$envMachineWorkgroup = (Get-WmiObject -Class 'Win32_ComputerSystem' -ErrorAction 'SilentlyContinue').Domain | Where-Object { $_ } | ForEach-Object { $_.ToUpper() } +} +[string]$envMachineDNSDomain = [Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName | Where-Object { $_ } | ForEach-Object { $_.ToLower() } +[string]$envUserDNSDomain = $env:USERDNSDOMAIN | Where-Object { $_ } | ForEach-Object { $_.ToLower() } +Try { + [string]$envUserDomain = [Environment]::UserDomainName.ToUpper() +} +Catch { } + +## Variables: Operating System +[psobject]$envOS = Get-WmiObject -Class 'Win32_OperatingSystem' -ErrorAction 'SilentlyContinue' +[string]$envOSName = $envOS.Caption.Trim() +[string]$envOSServicePack = $envOS.CSDVersion +[version]$envOSVersion = $envOS.Version +[string]$envOSVersionMajor = $envOSVersion.Major +[string]$envOSVersionMinor = $envOSVersion.Minor +[string]$envOSVersionBuild = $envOSVersion.Build +If ((Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction 'SilentlyContinue').PSObject.Properties.Name -contains 'UBR') { + [string]$envOSVersionRevision = (Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'UBR' -ErrorAction 'SilentlyContinue').UBR +} +ElseIf ((Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction 'SilentlyContinue').PSObject.Properties.Name -contains 'BuildLabEx') { + [string]$envOSVersionRevision = ,((Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'BuildLabEx' -ErrorAction 'SilentlyContinue').BuildLabEx -split '\.') | ForEach-Object { $_[1] } +} +If ($envOSVersionRevision -notmatch '^[\d\.]+$') { $envOSVersionRevision = '' } +If ($envOSVersionRevision) { [string]$envOSVersion = "$($envOSVersion.ToString()).$envOSVersionRevision" } Else { [string]$envOSVersion = "$($envOSVersion.ToString())" } +# Get the operating system type +[int32]$envOSProductType = $envOS.ProductType +[boolean]$IsServerOS = [boolean]($envOSProductType -eq 3) +[boolean]$IsDomainControllerOS = [boolean]($envOSProductType -eq 2) +[boolean]$IsWorkStationOS = [boolean]($envOSProductType -eq 1) +Switch ($envOSProductType) { + 3 { [string]$envOSProductTypeName = 'Server' } + 2 { [string]$envOSProductTypeName = 'Domain Controller' } + 1 { [string]$envOSProductTypeName = 'Workstation' } + Default { [string]$envOSProductTypeName = 'Unknown' } +} +# Get the OS Architecture +[boolean]$Is64Bit = [boolean]((Get-WmiObject -Class 'Win32_Processor' -ErrorAction 'SilentlyContinue' | Where-Object { $_.DeviceID -eq 'CPU0' } | Select-Object -ExpandProperty 'AddressWidth') -eq 64) +If ($Is64Bit) { [string]$envOSArchitecture = '64-bit' } Else { [string]$envOSArchitecture = '32-bit' } + +## Variables: Current Process Architecture +[boolean]$Is64BitProcess = [boolean]([IntPtr]::Size -eq 8) +If ($Is64BitProcess) { [string]$psArchitecture = 'x64' } Else { [string]$psArchitecture = 'x86' } + +## Variables: Get Normalized ProgramFiles and CommonProgramFiles Paths +[string]$envProgramFiles = '' +[string]$envProgramFilesX86 = '' +[string]$envCommonProgramFiles = '' +[string]$envCommonProgramFilesX86 = '' +If ($Is64Bit) { + If ($Is64BitProcess) { + [string]$envProgramFiles = [Environment]::GetFolderPath('ProgramFiles') + [string]$envCommonProgramFiles = [Environment]::GetFolderPath('CommonProgramFiles') + } + Else { + [string]$envProgramFiles = [Environment]::GetEnvironmentVariable('ProgramW6432') + [string]$envCommonProgramFiles = [Environment]::GetEnvironmentVariable('CommonProgramW6432') + } + ## Powershell 2 doesn't support X86 folders so need to use variables instead + try { + [string]$envProgramFilesX86 = [Environment]::GetFolderPath('ProgramFilesX86') + [string]$envCommonProgramFilesX86 = [Environment]::GetFolderPath('CommonProgramFilesX86') + } + catch { + [string]$envProgramFilesX86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)') + [string]$envCommonProgramFilesX86 = [Environment]::GetEnvironmentVariable('CommonProgramFiles(x86)') + } +} +Else { + [string]$envProgramFiles = [Environment]::GetFolderPath('ProgramFiles') + [string]$envProgramFilesX86 = $envProgramFiles + [string]$envCommonProgramFiles = [Environment]::GetFolderPath('CommonProgramFiles') + [string]$envCommonProgramFilesX86 = $envCommonProgramFiles +} + +## Variables: Hardware +[int32]$envSystemRAM = Get-WMIObject -Class Win32_PhysicalMemory -ComputerName $env:COMPUTERNAME -ErrorAction 'SilentlyContinue' | Measure-Object -Property Capacity -Sum -ErrorAction SilentlyContinue | ForEach-Object {[Math]::Round(($_.sum / 1GB),2)} + +## Variables: PowerShell And CLR (.NET) Versions +[hashtable]$envPSVersionTable = $PSVersionTable +# PowerShell Version +[version]$envPSVersion = $envPSVersionTable.PSVersion +[string]$envPSVersionMajor = $envPSVersion.Major +[string]$envPSVersionMinor = $envPSVersion.Minor +[string]$envPSVersionBuild = $envPSVersion.Build +[string]$envPSVersionRevision = $envPSVersion.Revision +[string]$envPSVersion = $envPSVersion.ToString() +# CLR (.NET) Version used by PowerShell +[version]$envCLRVersion = $envPSVersionTable.CLRVersion +[string]$envCLRVersionMajor = $envCLRVersion.Major +[string]$envCLRVersionMinor = $envCLRVersion.Minor +[string]$envCLRVersionBuild = $envCLRVersion.Build +[string]$envCLRVersionRevision = $envCLRVersion.Revision +[string]$envCLRVersion = $envCLRVersion.ToString() + +## Variables: Permissions/Accounts +[Security.Principal.WindowsIdentity]$CurrentProcessToken = [Security.Principal.WindowsIdentity]::GetCurrent() +[Security.Principal.SecurityIdentifier]$CurrentProcessSID = $CurrentProcessToken.User +[string]$ProcessNTAccount = $CurrentProcessToken.Name +[string]$ProcessNTAccountSID = $CurrentProcessSID.Value +[boolean]$IsAdmin = [boolean]($CurrentProcessToken.Groups -contains [Security.Principal.SecurityIdentifier]'S-1-5-32-544') +[boolean]$IsLocalSystemAccount = $CurrentProcessSID.IsWellKnown([Security.Principal.WellKnownSidType]'LocalSystemSid') +[boolean]$IsLocalServiceAccount = $CurrentProcessSID.IsWellKnown([Security.Principal.WellKnownSidType]'LocalServiceSid') +[boolean]$IsNetworkServiceAccount = $CurrentProcessSID.IsWellKnown([Security.Principal.WellKnownSidType]'NetworkServiceSid') +[boolean]$IsServiceAccount = [boolean]($CurrentProcessToken.Groups -contains [Security.Principal.SecurityIdentifier]'S-1-5-6') +[boolean]$IsProcessUserInteractive = [Environment]::UserInteractive +[string]$LocalSystemNTAccount = (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ([Security.Principal.WellKnownSidType]::'LocalSystemSid', $null)).Translate([Security.Principal.NTAccount]).Value +# Check if script is running in session zero +If ($IsLocalSystemAccount -or $IsLocalServiceAccount -or $IsNetworkServiceAccount -or $IsServiceAccount) { $SessionZero = $true } Else { $SessionZero = $false } + +## Variables: Script Name and Script Paths +[string]$scriptPath = $MyInvocation.MyCommand.Definition +[string]$scriptName = [IO.Path]::GetFileNameWithoutExtension($scriptPath) +[string]$scriptFileName = Split-Path -Path $scriptPath -Leaf +[string]$scriptRoot = Split-Path -Path $scriptPath -Parent +[string]$invokingScript = (Get-Variable -Name 'MyInvocation').Value.ScriptName +# Get the invoking script directory +If ($invokingScript) { + # If this script was invoked by another script + [string]$scriptParentPath = Split-Path -Path $invokingScript -Parent +} +Else { + # If this script was not invoked by another script, fall back to the directory one level above this script + [string]$scriptParentPath = (Get-Item -LiteralPath $scriptRoot).Parent.FullName +} + +## Variables: App Deploy Script Dependency Files +[string]$appDeployConfigFile = Join-Path -Path $scriptRoot -ChildPath 'AppDeployToolkitConfig.xml' +[string]$appDeployCustomTypesSourceCode = Join-Path -Path $scriptRoot -ChildPath 'AppDeployToolkitMain.cs' +If (-not (Test-Path -LiteralPath $appDeployConfigFile -PathType 'Leaf')) { Throw 'App Deploy XML configuration file not found.' } +If (-not (Test-Path -LiteralPath $appDeployCustomTypesSourceCode -PathType 'Leaf')) { Throw 'App Deploy custom types source code file not found.' } + +# App Deploy Optional Extensions File +[string]$appDeployToolkitDotSourceExtensions = 'AppDeployToolkitExtensions.ps1' + +## Import variables from XML configuration file +[Xml.XmlDocument]$xmlConfigFile = Get-Content -LiteralPath $AppDeployConfigFile -Encoding UTF8 +[Xml.XmlElement]$xmlConfig = $xmlConfigFile.AppDeployToolkit_Config +# Get Config File Details +[Xml.XmlElement]$configConfigDetails = $xmlConfig.Config_File +[string]$configConfigVersion = [version]$configConfigDetails.Config_Version +[string]$configConfigDate = $configConfigDetails.Config_Date + +# Get Banner and Icon details +[Xml.XmlElement]$xmlBannerIconOptions = $xmlConfig.BannerIcon_Options +[string]$configBannerIconFileName = $xmlBannerIconOptions.Icon_Filename +[string]$configBannerIconBannerName = $xmlBannerIconOptions.Banner_Filename +[Int32]$appDeployLogoBannerMaxHeight = $xmlBannerIconOptions.Banner_MaxHeight + +[string]$appDeployLogoIcon = Join-Path -Path $scriptRoot -ChildPath $configBannerIconFileName +[string]$appDeployLogoBanner = Join-Path -Path $scriptRoot -ChildPath $configBannerIconBannerName +# Check that dependency files are present +If (-not (Test-Path -LiteralPath $appDeployLogoIcon -PathType 'Leaf')) { Throw 'App Deploy logo icon file not found.' } +If (-not (Test-Path -LiteralPath $appDeployLogoBanner -PathType 'Leaf')) { Throw 'App Deploy logo banner file not found.' } + +Add-Type -AssemblyName 'System.Drawing' -ErrorAction 'Stop' +[System.Drawing.Bitmap]$appDeployLogoBannerObject = New-Object System.Drawing.Bitmap $appDeployLogoBanner +[Int32]$appDeployLogoBannerBaseHeight = 50 + +[Int32]$appDeployLogoBannerHeight = $appDeployLogoBannerObject.Height +if ($appDeployLogoBannerHeight -gt $appDeployLogoBannerMaxHeight) { + $appDeployLogoBannerHeight = $appDeployLogoBannerMaxHeight +} +[Int32]$appDeployLogoBannerHeightDifference = $appDeployLogoBannerHeight - $appDeployLogoBannerBaseHeight + +# Get Toolkit Options +[Xml.XmlElement]$xmlToolkitOptions = $xmlConfig.Toolkit_Options +[boolean]$configToolkitRequireAdmin = [boolean]::Parse($xmlToolkitOptions.Toolkit_RequireAdmin) +[string]$configToolkitTempPath = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_TempPath) +[string]$configToolkitRegPath = $xmlToolkitOptions.Toolkit_RegPath +[string]$configToolkitLogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_LogPath) +[boolean]$configToolkitCompressLogs = [boolean]::Parse($xmlToolkitOptions.Toolkit_CompressLogs) +[string]$configToolkitLogStyle = $xmlToolkitOptions.Toolkit_LogStyle +[double]$configToolkitLogMaxSize = $xmlToolkitOptions.Toolkit_LogMaxSize +[boolean]$configToolkitLogWriteToHost = [boolean]::Parse($xmlToolkitOptions.Toolkit_LogWriteToHost) +[boolean]$configToolkitLogDebugMessage = [boolean]::Parse($xmlToolkitOptions.Toolkit_LogDebugMessage) +# Get MSI Options +[Xml.XmlElement]$xmlConfigMSIOptions = $xmlConfig.MSI_Options +[string]$configMSILoggingOptions = $xmlConfigMSIOptions.MSI_LoggingOptions +[string]$configMSIInstallParams = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_InstallParams) +[string]$configMSISilentParams = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_SilentParams) +[string]$configMSIUninstallParams = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_UninstallParams) +[string]$configMSILogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_LogPath) +[int32]$configMSIMutexWaitTime = $xmlConfigMSIOptions.MSI_MutexWaitTime +# Change paths to user accessible ones if RequireAdmin is false +If ($configToolkitRequireAdmin -eq $false){ + If ($xmlToolkitOptions.Toolkit_TempPathNoAdminRights) { + [string]$configToolkitTempPath = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_TempPathNoAdminRights) + } + If ($xmlToolkitOptions.Toolkit_RegPathNoAdminRights) { + [string]$configToolkitRegPath = $xmlToolkitOptions.Toolkit_RegPathNoAdminRights + } + If ($xmlToolkitOptions.Toolkit_LogPathNoAdminRights) { + [string]$configToolkitLogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlToolkitOptions.Toolkit_LogPathNoAdminRights) + } + If ($xmlConfigMSIOptions.MSI_LogPathNoAdminRights) { + [string]$configMSILogDir = $ExecutionContext.InvokeCommand.ExpandString($xmlConfigMSIOptions.MSI_LogPathNoAdminRights) + } +} +# Get UI Options +[Xml.XmlElement]$xmlConfigUIOptions = $xmlConfig.UI_Options +[string]$configInstallationUILanguageOverride = $xmlConfigUIOptions.InstallationUI_LanguageOverride +[boolean]$configShowBalloonNotifications = [boolean]::Parse($xmlConfigUIOptions.ShowBalloonNotifications) +[int32]$configInstallationUITimeout = $xmlConfigUIOptions.InstallationUI_Timeout +[int32]$configInstallationUIExitCode = $xmlConfigUIOptions.InstallationUI_ExitCode +[int32]$configInstallationDeferExitCode = $xmlConfigUIOptions.InstallationDefer_ExitCode +[int32]$configInstallationPersistInterval = $xmlConfigUIOptions.InstallationPrompt_PersistInterval +[int32]$configInstallationRestartPersistInterval = $xmlConfigUIOptions.InstallationRestartPrompt_PersistInterval +[int32]$configInstallationPromptToSave = $xmlConfigUIOptions.InstallationPromptToSave_Timeout +[boolean]$configInstallationWelcomePromptDynamicRunningProcessEvaluation = [boolean]::Parse($xmlConfigUIOptions.InstallationWelcomePrompt_DynamicRunningProcessEvaluation) +[int32]$configInstallationWelcomePromptDynamicRunningProcessEvaluationInterval = $xmlConfigUIOptions.InstallationWelcomePrompt_DynamicRunningProcessEvaluationInterval +# Define ScriptBlock for Loading Message UI Language Options (default for English if no localization found) +[scriptblock]$xmlLoadLocalizedUIMessages = { + # If a user is logged on, then get primary UI language for logged on user (even if running in session 0) + If ($RunAsActiveUser) { + # Read language defined by Group Policy + If (-not $HKULanguages) { + [string[]]$HKULanguages = Get-RegistryKey -Key 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\MUI\Settings' -Value 'PreferredUILanguages' + } + If (-not $HKULanguages) { + [string[]]$HKULanguages = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\Control Panel\Desktop' -Value 'PreferredUILanguages' -SID $RunAsActiveUser.SID + } + # Read language for Win Vista & higher machines + If (-not $HKULanguages) { + [string[]]$HKULanguages = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\Desktop' -Value 'PreferredUILanguages' -SID $RunAsActiveUser.SID + } + If (-not $HKULanguages) { + [string[]]$HKULanguages = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\Desktop\MuiCached' -Value 'MachinePreferredUILanguages' -SID $RunAsActiveUser.SID + } + If (-not $HKULanguages) { + [string[]]$HKULanguages = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\International' -Value 'LocaleName' -SID $RunAsActiveUser.SID + } + # Read language for Win XP machines + If (-not $HKULanguages) { + [string]$HKULocale = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\International' -Value 'Locale' -SID $RunAsActiveUser.SID + If ($HKULocale) { + [int32]$HKULocale = [Convert]::ToInt32('0x' + $HKULocale, 16) + [string[]]$HKULanguages = ([Globalization.CultureInfo]($HKULocale)).Name + } + } + If ($HKULanguages) { + [Globalization.CultureInfo]$PrimaryWindowsUILanguage = [Globalization.CultureInfo]($HKULanguages[0]) + [string]$HKUPrimaryLanguageShort = $PrimaryWindowsUILanguage.TwoLetterISOLanguageName.ToUpper() + + # If the detected language is Chinese, determine if it is simplified or traditional Chinese + If ($HKUPrimaryLanguageShort -eq 'ZH') { + If ($PrimaryWindowsUILanguage.EnglishName -match 'Simplified') { + [string]$HKUPrimaryLanguageShort = 'ZH-Hans' + } + If ($PrimaryWindowsUILanguage.EnglishName -match 'Traditional') { + [string]$HKUPrimaryLanguageShort = 'ZH-Hant' + } + } + + # If the detected language is Portuguese, determine if it is Brazilian Portuguese + If ($HKUPrimaryLanguageShort -eq 'PT') { + If ($PrimaryWindowsUILanguage.ThreeLetterWindowsLanguageName -eq 'PTB') { + [string]$HKUPrimaryLanguageShort = 'PT-BR' + } + } + } + } + + If ($HKUPrimaryLanguageShort) { + # Use the primary UI language of the logged in user + [string]$xmlUIMessageLanguage = "UI_Messages_$HKUPrimaryLanguageShort" + } + Else { + # Default to UI language of the account executing current process (even if it is the SYSTEM account) + [string]$xmlUIMessageLanguage = "UI_Messages_$currentLanguage" + } + # Default to English if the detected UI language is not available in the XMl config file + If (-not ($xmlConfig.$xmlUIMessageLanguage)) { [string]$xmlUIMessageLanguage = 'UI_Messages_EN' } + # Override the detected language if the override option was specified in the XML config file + If ($configInstallationUILanguageOverride) { [string]$xmlUIMessageLanguage = "UI_Messages_$configInstallationUILanguageOverride" } + + [Xml.XmlElement]$xmlUIMessages = $xmlConfig.$xmlUIMessageLanguage + [string]$configDiskSpaceMessage = $xmlUIMessages.DiskSpace_Message + [string]$configBalloonTextStart = $xmlUIMessages.BalloonText_Start + [string]$configBalloonTextComplete = $xmlUIMessages.BalloonText_Complete + [string]$configBalloonTextRestartRequired = $xmlUIMessages.BalloonText_RestartRequired + [string]$configBalloonTextFastRetry = $xmlUIMessages.BalloonText_FastRetry + [string]$configBalloonTextError = $xmlUIMessages.BalloonText_Error + [string]$configProgressMessageInstall = $xmlUIMessages.Progress_MessageInstall + [string]$configProgressMessageUninstall = $xmlUIMessages.Progress_MessageUninstall + [string]$configProgressMessageRepair = $xmlUIMessages.Progress_MessageRepair + [string]$configClosePromptMessage = $xmlUIMessages.ClosePrompt_Message + [string]$configClosePromptButtonClose = $xmlUIMessages.ClosePrompt_ButtonClose + [string]$configClosePromptButtonDefer = $xmlUIMessages.ClosePrompt_ButtonDefer + [string]$configClosePromptButtonContinue = $xmlUIMessages.ClosePrompt_ButtonContinue + [string]$configClosePromptButtonContinueTooltip = $xmlUIMessages.ClosePrompt_ButtonContinueTooltip + [string]$configClosePromptCountdownMessage = $xmlUIMessages.ClosePrompt_CountdownMessage + [string]$configDeferPromptWelcomeMessage = $xmlUIMessages.DeferPrompt_WelcomeMessage + [string]$configDeferPromptExpiryMessage = $xmlUIMessages.DeferPrompt_ExpiryMessage + [string]$configDeferPromptWarningMessage = $xmlUIMessages.DeferPrompt_WarningMessage + [string]$configDeferPromptRemainingDeferrals = $xmlUIMessages.DeferPrompt_RemainingDeferrals + [string]$configDeferPromptDeadline = $xmlUIMessages.DeferPrompt_Deadline + [string]$configBlockExecutionMessage = $xmlUIMessages.BlockExecution_Message + [string]$configDeploymentTypeInstall = $xmlUIMessages.DeploymentType_Install + [string]$configDeploymentTypeUnInstall = $xmlUIMessages.DeploymentType_UnInstall + [string]$configDeploymentTypeRepair = $xmlUIMessages.DeploymentType_Repair + [string]$configRestartPromptTitle = $xmlUIMessages.RestartPrompt_Title + [string]$configRestartPromptMessage = $xmlUIMessages.RestartPrompt_Message + [string]$configRestartPromptMessageTime = $xmlUIMessages.RestartPrompt_MessageTime + [string]$configRestartPromptMessageRestart = $xmlUIMessages.RestartPrompt_MessageRestart + [string]$configRestartPromptTimeRemaining = $xmlUIMessages.RestartPrompt_TimeRemaining + [string]$configRestartPromptButtonRestartLater = $xmlUIMessages.RestartPrompt_ButtonRestartLater + [string]$configRestartPromptButtonRestartNow = $xmlUIMessages.RestartPrompt_ButtonRestartNow + [string]$configWelcomePromptCountdownMessage = $xmlUIMessages.WelcomePrompt_CountdownMessage + [string]$configWelcomePromptCustomMessage = $xmlUIMessages.WelcomePrompt_CustomMessage +} + +## Variables: Script Directories +[string]$dirFiles = Join-Path -Path $scriptParentPath -ChildPath 'Files' +[string]$dirSupportFiles = Join-Path -Path $scriptParentPath -ChildPath 'SupportFiles' +[string]$dirAppDeployTemp = Join-Path -Path $configToolkitTempPath -ChildPath $appDeployToolkitName + +## Set the deployment type to "Install" if it has not been specified +If (-not $deploymentType) { [string]$deploymentType = 'Install' } + +## Variables: Executables +[string]$exeWusa = "$envWinDir\System32\wusa.exe" # Installs Standalone Windows Updates +[string]$exeMsiexec = "$envWinDir\System32\msiexec.exe" # Installs MSI Installers +[string]$exeSchTasks = "$envWinDir\System32\schtasks.exe" # Manages Scheduled Tasks + +## Variables: RegEx Patterns +[string]$MSIProductCodeRegExPattern = '^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$' + +## Variables: Invalid FileName Characters +[char[]]$invalidFileNameChars = [IO.Path]::GetinvalidFileNameChars() + +## Variables: Registry Keys +# Registry keys for native and WOW64 applications +[string[]]$regKeyApplications = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall' +If ($is64Bit) { + [string]$regKeyLotusNotes = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Lotus\Notes' +} +Else { + [string]$regKeyLotusNotes = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Lotus\Notes' +} +[string]$regKeyAppExecution = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options' + +## COM Objects: Initialize +[__comobject]$Shell = New-Object -ComObject 'WScript.Shell' -ErrorAction 'SilentlyContinue' +[__comobject]$ShellApp = New-Object -ComObject 'Shell.Application' -ErrorAction 'SilentlyContinue' + +## Variables: Reset/Remove Variables +[boolean]$msiRebootDetected = $false +[boolean]$BlockExecution = $false +[boolean]$installationStarted = $false +[boolean]$runningTaskSequence = $false +If (Test-Path -LiteralPath 'variable:welcomeTimer') { Remove-Variable -Name 'welcomeTimer' -Scope 'Script'} +# Reset the deferral history +If (Test-Path -LiteralPath 'variable:deferHistory') { Remove-Variable -Name 'deferHistory' } +If (Test-Path -LiteralPath 'variable:deferTimes') { Remove-Variable -Name 'deferTimes' } +If (Test-Path -LiteralPath 'variable:deferDays') { Remove-Variable -Name 'deferDays' } + +## Variables: System DPI Scale Factor +[scriptblock]$GetDisplayScaleFactor = { + # If a user is logged on, then get display scale factor for logged on user (even if running in session 0) + [boolean]$UserDisplayScaleFactor = $false + If ($RunAsActiveUser) { + [int32]$dpiPixels = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics' -Value 'AppliedDPI' -SID $RunAsActiveUser.SID + If (-not ([string]$dpiPixels)) { + [int32]$dpiPixels = Get-RegistryKey -Key 'Registry::HKEY_CURRENT_USER\Control Panel\Desktop' -Value 'LogPixels' -SID $RunAsActiveUser.SID + } + [boolean]$UserDisplayScaleFactor = $true + } + If (-not ([string]$dpiPixels)) { + # This registry setting only exists if system scale factor has been changed at least once + [int32]$dpiPixels = Get-RegistryKey -Key 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI' -Value 'LogPixels' + [boolean]$UserDisplayScaleFactor = $false + } + Switch ($dpiPixels) { + 96 { [int32]$dpiScale = 100 } + 120 { [int32]$dpiScale = 125 } + 144 { [int32]$dpiScale = 150 } + 192 { [int32]$dpiScale = 200 } + Default { [int32]$dpiScale = 100 } + } +} +#endregion +##*============================================= +##* END VARIABLE DECLARATION +##*============================================= + +##*============================================= +##* FUNCTION LISTINGS +##*============================================= +#region FunctionListings + +#region Function Write-FunctionHeaderOrFooter +Function Write-FunctionHeaderOrFooter { +<# +.SYNOPSIS + Write the function header or footer to the log upon first entering or exiting a function. +.DESCRIPTION + Write the "Function Start" message, the bound parameters the function was invoked with, or the "Function End" message when entering or exiting a function. + Messages are debug messages so will only be logged if LogDebugMessage option is enabled in XML config file. +.PARAMETER CmdletName + The name of the function this function is invoked from. +.PARAMETER CmdletBoundParameters + The bound parameters of the function this function is invoked from. +.PARAMETER Header + Write the function header. +.PARAMETER Footer + Write the function footer. +.EXAMPLE + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header +.EXAMPLE + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$CmdletName, + [Parameter(Mandatory=$true,ParameterSetName='Header')] + [AllowEmptyCollection()] + [hashtable]$CmdletBoundParameters, + [Parameter(Mandatory=$true,ParameterSetName='Header')] + [switch]$Header, + [Parameter(Mandatory=$true,ParameterSetName='Footer')] + [switch]$Footer + ) + + If ($Header) { + Write-Log -Message 'Function Start' -Source ${CmdletName} -DebugMessage + + ## Get the parameters that the calling function was invoked with + [string]$CmdletBoundParameters = $CmdletBoundParameters | Format-Table -Property @{ Label = 'Parameter'; Expression = { "[-$($_.Key)]" } }, @{ Label = 'Value'; Expression = { $_.Value }; Alignment = 'Left' }, @{ Label = 'Type'; Expression = { $_.Value.GetType().Name }; Alignment = 'Left' } -AutoSize -Wrap | Out-String + If ($CmdletBoundParameters) { + Write-Log -Message "Function invoked with bound parameter(s): `n$CmdletBoundParameters" -Source ${CmdletName} -DebugMessage + } + Else { + Write-Log -Message 'Function invoked without any bound parameters.' -Source ${CmdletName} -DebugMessage + } + } + ElseIf ($Footer) { + Write-Log -Message 'Function End' -Source ${CmdletName} -DebugMessage + } +} +#endregion +#region Function Execute-MSP +Function Execute-MSP { +<# +.SYNOPSIS + Reads SummaryInfo targeted product codes in MSP file and determines if the MSP file applies to any installed products + If a valid installed product is found, triggers the Execute-MSI function to patch the installation. Uses default config MSI parameters. +.PARAMETER Path + Path to the msp file +.PARAMETER AddParameters + Additional parameters +.EXAMPLE + Execute-MSP -Path 'Adobe_Reader_11.0.3_EN.msp' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,HelpMessage='Please enter the path to the MSP file')] + [ValidateScript({('.msp' -contains [IO.Path]::GetExtension($_))})] + [Alias('FilePath')] + [string]$Path, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$AddParameters + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## If the MSP is in the Files directory, set the full path to the MSP + If (Test-Path -LiteralPath (Join-Path -Path $dirFiles -ChildPath $path -ErrorAction 'SilentlyContinue') -PathType 'Leaf' -ErrorAction 'SilentlyContinue') { + [string]$mspFile = Join-Path -Path $dirFiles -ChildPath $path + } + ElseIf (Test-Path -LiteralPath $Path -ErrorAction 'SilentlyContinue') { + [string]$mspFile = (Get-Item -LiteralPath $Path).FullName + } + Else { + Write-Log -Message "Failed to find MSP file [$path]." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to find MSP file [$path]." + } + Continue + } + Write-Log -Message 'Checking MSP file for valid product codes' -Source ${CmdletName} + + [boolean]$IsMSPNeeded = $false + + $Installer = New-Object -com WindowsInstaller.Installer + $Database = $Installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, $($mspFile,([int32]32))) + [__comobject]$SummaryInformation = Get-ObjectProperty -InputObject $Database -PropertyName 'SummaryInformation' + [hashtable]$SummaryInfoProperty = @{} + $all = (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(7)).Split(";") + Foreach($FormattedProductCode in $all) { + [psobject]$MSIInstalled = Get-InstalledApplication -ProductCode $FormattedProductCode + If ($MSIInstalled) {[boolean]$IsMSPNeeded = $true } + } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($SummaryInformation) } Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($DataBase) } Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($Installer) } Catch { } + If ($IsMSPNeeded) { + If ($AddParameters) { + Execute-MSI -Action Patch -Path $Path -AddParameters $AddParameters + } + Else { + Execute-MSI -Action Patch -Path $Path + } + } + } +} +#endregion + +#region Function Write-Log +Function Write-Log { +<# +.SYNOPSIS + Write messages to a log file in CMTrace.exe compatible format or Legacy text file format. +.DESCRIPTION + Write messages to a log file in CMTrace.exe compatible format or Legacy text file format and optionally display in the console. +.PARAMETER Message + The message to write to the log file or output to the console. +.PARAMETER Severity + Defines message type. When writing to console or CMTrace.exe log format, it allows highlighting of message type. + Options: 1 = Information (default), 2 = Warning (highlighted in yellow), 3 = Error (highlighted in red) +.PARAMETER Source + The source of the message being logged. +.PARAMETER ScriptSection + The heading for the portion of the script that is being executed. Default is: $script:installPhase. +.PARAMETER LogType + Choose whether to write a CMTrace.exe compatible log file or a Legacy text log file. +.PARAMETER LogFileDirectory + Set the directory where the log file will be saved. +.PARAMETER LogFileName + Set the name of the log file. +.PARAMETER MaxLogFileSizeMB + Maximum file size limit for log file in megabytes (MB). Default is 10 MB. +.PARAMETER WriteHost + Write the log message to the console. +.PARAMETER ContinueOnError + Suppress writing log message to console on failure to write message to log file. Default is: $true. +.PARAMETER PassThru + Return the message that was passed to the function +.PARAMETER DebugMessage + Specifies that the message is a debug message. Debug messages only get logged if -LogDebugMessage is set to $true. +.PARAMETER LogDebugMessage + Debug messages only get logged if this parameter is set to $true in the config XML file. +.EXAMPLE + Write-Log -Message "Installing patch MS15-031" -Source 'Add-Patch' -LogType 'CMTrace' +.EXAMPLE + Write-Log -Message "Script is running on Windows 8" -Source 'Test-ValidOS' -LogType 'Legacy' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [AllowEmptyCollection()] + [Alias('Text')] + [string[]]$Message, + [Parameter(Mandatory=$false,Position=1)] + [ValidateRange(1,3)] + [int16]$Severity = 1, + [Parameter(Mandatory=$false,Position=2)] + [ValidateNotNull()] + [string]$Source = $([string]$parentFunctionName = [IO.Path]::GetFileNameWithoutExtension((Get-Variable -Name MyInvocation -Scope 1 -ErrorAction SilentlyContinue).Value.MyCommand.Name); If($parentFunctionName) {$parentFunctionName} Else {'Unknown'}), + [Parameter(Mandatory=$false,Position=3)] + [ValidateNotNullorEmpty()] + [string]$ScriptSection = $script:installPhase, + [Parameter(Mandatory=$false,Position=4)] + [ValidateSet('CMTrace','Legacy')] + [string]$LogType = $configToolkitLogStyle, + [Parameter(Mandatory=$false,Position=5)] + [ValidateNotNullorEmpty()] + [string]$LogFileDirectory = $(If ($configToolkitCompressLogs) { $logTempFolder } Else { $configToolkitLogDir }), + [Parameter(Mandatory=$false,Position=6)] + [ValidateNotNullorEmpty()] + [string]$LogFileName = $logName, + [Parameter(Mandatory=$false,Position=7)] + [ValidateNotNullorEmpty()] + [decimal]$MaxLogFileSizeMB = $configToolkitLogMaxSize, + [Parameter(Mandatory=$false,Position=8)] + [ValidateNotNullorEmpty()] + [boolean]$WriteHost = $configToolkitLogWriteToHost, + [Parameter(Mandatory=$false,Position=9)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true, + [Parameter(Mandatory=$false,Position=10)] + [switch]$PassThru = $false, + [Parameter(Mandatory=$false,Position=11)] + [switch]$DebugMessage = $false, + [Parameter(Mandatory=$false,Position=12)] + [boolean]$LogDebugMessage = $configToolkitLogDebugMessage + ) + + Begin { + ## Get the name of this function + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + ## Logging Variables + # Log file date/time + [string]$LogTime = (Get-Date -Format 'HH\:mm\:ss.fff').ToString() + [string]$LogDate = (Get-Date -Format 'MM-dd-yyyy').ToString() + If (-not (Test-Path -LiteralPath 'variable:LogTimeZoneBias')) { [int32]$script:LogTimeZoneBias = [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes } + [string]$LogTimePlusBias = $LogTime + $script:LogTimeZoneBias + # Initialize variables + [boolean]$ExitLoggingFunction = $false + If (-not (Test-Path -LiteralPath 'variable:DisableLogging')) { $DisableLogging = $false } + # Check if the script section is defined + [boolean]$ScriptSectionDefined = [boolean](-not [string]::IsNullOrEmpty($ScriptSection)) + # Get the file name of the source script + Try { + If ($script:MyInvocation.Value.ScriptName) { + [string]$ScriptSource = Split-Path -Path $script:MyInvocation.Value.ScriptName -Leaf -ErrorAction 'Stop' + } + Else { + [string]$ScriptSource = Split-Path -Path $script:MyInvocation.MyCommand.Definition -Leaf -ErrorAction 'Stop' + } + } + Catch { + $ScriptSource = '' + } + + ## Create script block for generating CMTrace.exe compatible log entry + [scriptblock]$CMTraceLogString = { + Param ( + [string]$lMessage, + [string]$lSource, + [int16]$lSeverity + ) + "" + "" + } + + ## Create script block for writing log entry to the console + [scriptblock]$WriteLogLineToHost = { + Param ( + [string]$lTextLogLine, + [int16]$lSeverity + ) + If ($WriteHost) { + # Only output using color options if running in a host which supports colors. + If ($Host.UI.RawUI.ForegroundColor) { + Switch ($lSeverity) { + 3 { Write-Host -Object $lTextLogLine -ForegroundColor 'Red' -BackgroundColor 'Black' } + 2 { Write-Host -Object $lTextLogLine -ForegroundColor 'Yellow' -BackgroundColor 'Black' } + 1 { Write-Host -Object $lTextLogLine } + } + } + # If executing "powershell.exe -File .ps1 > log.txt", then all the Write-Host calls are converted to Write-Output calls so that they are included in the text log. + Else { + Write-Output -InputObject $lTextLogLine + } + } + } + + ## Exit function if it is a debug message and logging debug messages is not enabled in the config XML file + If (($DebugMessage) -and (-not $LogDebugMessage)) { [boolean]$ExitLoggingFunction = $true; Return } + ## Exit function if logging to file is disabled and logging to console host is disabled + If (($DisableLogging) -and (-not $WriteHost)) { [boolean]$ExitLoggingFunction = $true; Return } + ## Exit Begin block if logging is disabled + If ($DisableLogging) { Return } + ## Exit function function if it is an [Initialization] message and the toolkit has been relaunched + If (($AsyncToolkitLaunch) -and ($ScriptSection -eq 'Initialization')) { [boolean]$ExitLoggingFunction = $true; Return } + + ## Create the directory where the log file will be saved + If (-not (Test-Path -LiteralPath $LogFileDirectory -PathType 'Container')) { + Try { + $null = New-Item -Path $LogFileDirectory -Type 'Directory' -Force -ErrorAction 'Stop' + } + Catch { + [boolean]$ExitLoggingFunction = $true + # If error creating directory, write message to console + If (-not $ContinueOnError) { + Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the log directory [$LogFileDirectory]. `n$(Resolve-Error)" -ForegroundColor 'Red' + } + Return + } + } + + ## Assemble the fully qualified path to the log file + [string]$LogFilePath = Join-Path -Path $LogFileDirectory -ChildPath $LogFileName + } + Process { + ## Exit function if logging is disabled + If ($ExitLoggingFunction) { Return } + + ForEach ($Msg in $Message) { + ## If the message is not $null or empty, create the log entry for the different logging methods + [string]$CMTraceMsg = '' + [string]$ConsoleLogLine = '' + [string]$LegacyTextLogLine = '' + If ($Msg) { + # Create the CMTrace log message + If ($ScriptSectionDefined) { [string]$CMTraceMsg = "[$ScriptSection] :: $Msg" } + + # Create a Console and Legacy "text" log entry + [string]$LegacyMsg = "[$LogDate $LogTime]" + If ($ScriptSectionDefined) { [string]$LegacyMsg += " [$ScriptSection]" } + If ($Source) { + [string]$ConsoleLogLine = "$LegacyMsg [$Source] :: $Msg" + Switch ($Severity) { + 3 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Error] :: $Msg" } + 2 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Warning] :: $Msg" } + 1 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Info] :: $Msg" } + } + } + Else { + [string]$ConsoleLogLine = "$LegacyMsg :: $Msg" + Switch ($Severity) { + 3 { [string]$LegacyTextLogLine = "$LegacyMsg [Error] :: $Msg" } + 2 { [string]$LegacyTextLogLine = "$LegacyMsg [Warning] :: $Msg" } + 1 { [string]$LegacyTextLogLine = "$LegacyMsg [Info] :: $Msg" } + } + } + } + + ## Execute script block to create the CMTrace.exe compatible log entry + [string]$CMTraceLogLine = & $CMTraceLogString -lMessage $CMTraceMsg -lSource $Source -lSeverity $Severity + + ## Choose which log type to write to file + If ($LogType -ieq 'CMTrace') { + [string]$LogLine = $CMTraceLogLine + } + Else { + [string]$LogLine = $LegacyTextLogLine + } + + ## Write the log entry to the log file if logging is not currently disabled + If (-not $DisableLogging) { + Try { + $LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Stop' + } + Catch { + If (-not $ContinueOnError) { + Write-Host -Object "[$LogDate $LogTime] [$ScriptSection] [${CmdletName}] :: Failed to write message [$Msg] to the log file [$LogFilePath]. `n$(Resolve-Error)" -ForegroundColor 'Red' + } + } + } + + ## Execute script block to write the log entry to the console if $WriteHost is $true + & $WriteLogLineToHost -lTextLogLine $ConsoleLogLine -lSeverity $Severity + } + } + End { + ## Archive log file if size is greater than $MaxLogFileSizeMB and $MaxLogFileSizeMB > 0 + Try { + If ((-not $ExitLoggingFunction) -and (-not $DisableLogging)) { + [IO.FileInfo]$LogFile = Get-ChildItem -LiteralPath $LogFilePath -ErrorAction 'Stop' + [decimal]$LogFileSizeMB = $LogFile.Length/1MB + If (($LogFileSizeMB -gt $MaxLogFileSizeMB) -and ($MaxLogFileSizeMB -gt 0)) { + ## Change the file extension to "lo_" + [string]$ArchivedOutLogFile = [IO.Path]::ChangeExtension($LogFilePath, 'lo_') + [hashtable]$ArchiveLogParams = @{ ScriptSection = $ScriptSection; Source = ${CmdletName}; Severity = 2; LogFileDirectory = $LogFileDirectory; LogFileName = $LogFileName; LogType = $LogType; MaxLogFileSizeMB = 0; WriteHost = $WriteHost; ContinueOnError = $ContinueOnError; PassThru = $false } + + ## Log message about archiving the log file + $ArchiveLogMessage = "Maximum log file size [$MaxLogFileSizeMB MB] reached. Rename log file to [$ArchivedOutLogFile]." + Write-Log -Message $ArchiveLogMessage @ArchiveLogParams + + ## Archive existing log file from .log to .lo_. Overwrites any existing .lo_ file. This is the same method SCCM uses for log files. + Move-Item -LiteralPath $LogFilePath -Destination $ArchivedOutLogFile -Force -ErrorAction 'Stop' + + ## Start new log file and Log message about archiving the old log file + $NewLogMessage = "Previous log file was renamed to [$ArchivedOutLogFile] because maximum log file size of [$MaxLogFileSizeMB MB] was reached." + Write-Log -Message $NewLogMessage @ArchiveLogParams + } + } + } + Catch { + ## If renaming of file fails, script will continue writing to log file even if size goes over the max file size + } + Finally { + If ($PassThru) { Write-Output -InputObject $Message } + } + } +} +#endregion + +#region Function Remove-InvalidFileNameChars +Function Remove-InvalidFileNameChars { + <# + .SYNOPSIS + Remove invalid characters from the supplied string. + .DESCRIPTION + Remove invalid characters from the supplied string and returns a valid filename as a string. + .EXAMPLE + Remove-InvalidFileNameChars -Name "Filename/\1" + .NOTES + This functions always returns a string however it can be empty if the name only contains invalid characters. + Do no use this command for an entire path as '\' is not a valid filename character. + .LINK + http://psappdeploytoolkit.com + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [AllowEmptyString()] + [string]$Name + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Output -InputObject (([char[]]$Name | Where-Object { $invalidFileNameChars -notcontains $_ }) -join '') + } + Catch { + Write-Log -Message "Failed to remove invalid characters from the supplied filename. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function New-ZipFile +Function New-ZipFile { +<# +.SYNOPSIS + Create a new zip archive or add content to an existing archive. +.DESCRIPTION + Create a new zip archive or add content to an existing archive by using the Shell object .CopyHere method. +.PARAMETER DestinationArchiveDirectoryPath + The path to the directory path where the zip archive will be saved. +.PARAMETER DestinationArchiveFileName + The name of the zip archive. +.PARAMETER SourceDirectoryPath + The path to the directory to be archived, specified as absolute paths. +.PARAMETER SourceFilePath + The path to the file to be archived, specified as absolute paths. +.PARAMETER RemoveSourceAfterArchiving + Remove the source path after successfully archiving the content. Default is: $false. +.PARAMETER OverWriteArchive + Overwrite the destination archive path if it already exists. Default is: $false. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default: $true. +.EXAMPLE + New-ZipFile -DestinationArchiveDirectoryPath 'E:\Testing' -DestinationArchiveFileName 'TestingLogs.zip' -SourceDirectory 'E:\Testing\Logs' +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding(DefaultParameterSetName='CreateFromDirectory')] + Param ( + [Parameter(Mandatory=$true,Position=0)] + [ValidateNotNullorEmpty()] + [string]$DestinationArchiveDirectoryPath, + [Parameter(Mandatory=$true,Position=1)] + [ValidateNotNullorEmpty()] + [string]$DestinationArchiveFileName, + [Parameter(Mandatory=$true,Position=2,ParameterSetName='CreateFromDirectory')] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Container' })] + [string[]]$SourceDirectoryPath, + [Parameter(Mandatory=$true,Position=2,ParameterSetName='CreateFromFile')] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [string[]]$SourceFilePath, + [Parameter(Mandatory=$false,Position=3)] + [ValidateNotNullorEmpty()] + [switch]$RemoveSourceAfterArchiving = $false, + [Parameter(Mandatory=$false,Position=4)] + [ValidateNotNullorEmpty()] + [switch]$OverWriteArchive = $false, + [Parameter(Mandatory=$false,Position=5)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## Remove invalid characters from the supplied filename + $DestinationArchiveFileName = Remove-InvalidFileNameChars -Name $DestinationArchiveFileName + If ($DestinationArchiveFileName.length -eq 0) { + Throw "Invalid filename characters replacement resulted into an empty string." + } + ## Get the full destination path where the archive will be stored + [string]$DestinationPath = Join-Path -Path $DestinationArchiveDirectoryPath -ChildPath $DestinationArchiveFileName -ErrorAction 'Stop' + Write-Log -Message "Create a zip archive with the requested content at destination path [$DestinationPath]." -Source ${CmdletName} + + ## If the destination archive already exists, delete it if the -OverWriteArchive option was selected + If (($OverWriteArchive) -and (Test-Path -LiteralPath $DestinationPath)) { + Write-Log -Message "An archive at the destination path already exists, deleting file [$DestinationPath]." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $DestinationPath -Force -ErrorAction 'Stop' + } + + ## If archive file does not exist, then create a zero-byte zip archive + If (-not (Test-Path -LiteralPath $DestinationPath)) { + ## Create a zero-byte file + Write-Log -Message "Create a zero-byte file [$DestinationPath]." -Source ${CmdletName} + $null = New-Item -Path $DestinationArchiveDirectoryPath -Name $DestinationArchiveFileName -ItemType 'File' -Force -ErrorAction 'Stop' + + ## Write the file header for a zip file to the zero-byte file + [byte[]]$ZipArchiveByteHeader = 80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + [IO.FileStream]$FileStream = New-Object -TypeName 'System.IO.FileStream' -ArgumentList ($DestinationPath, ([IO.FileMode]::Create)) + [IO.BinaryWriter]$BinaryWriter = New-Object -TypeName 'System.IO.BinaryWriter' -ArgumentList ($FileStream) + Write-Log -Message "Write the file header for a zip archive to the zero-byte file [$DestinationPath]." -Source ${CmdletName} + $null = $BinaryWriter.Write($ZipArchiveByteHeader) + $BinaryWriter.Close() + $FileStream.Close() + } + + ## Create a Shell object + [__comobject]$ShellApp = New-Object -ComObject 'Shell.Application' -ErrorAction 'Stop' + ## Create an object representing the archive file + [__comobject]$Archive = $ShellApp.NameSpace($DestinationPath) + + ## Create the archive file + If ($PSCmdlet.ParameterSetName -eq 'CreateFromDirectory') { + ## Create the archive file from a source directory + ForEach ($Directory in $SourceDirectoryPath) { + Try { + # Create an object representing the source directory + [__comobject]$CreateFromDirectory = $ShellApp.NameSpace($Directory) + # Copy all of the files and folders from the source directory to the archive + $null = $Archive.CopyHere($CreateFromDirectory.Items()) + # Wait for archive operation to complete. Archive file count property returns 0 if archive operation is in progress. + Write-Log -Message "Compressing [$($CreateFromDirectory.Count)] file(s) in source directory [$Directory] to destination path [$DestinationPath]..." -Source ${CmdletName} + Do { Start-Sleep -Milliseconds 250 } While ($Archive.Items().Count -eq 0) + } + Finally { + # Release the ComObject representing the source directory + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($CreateFromDirectory) + } + + # If option was selected, recursively delete the source directory after successfully archiving the contents + If ($RemoveSourceAfterArchiving) { + Try { + Write-Log -Message "Recursively delete the source directory [$Directory] as contents have been successfully archived." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $Directory -Recurse -Force -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to recursively delete the source directory [$Directory]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + } + } + Else { + ## Create the archive file from a list of one or more files + [IO.FileInfo[]]$SourceFilePath = [IO.FileInfo[]]$SourceFilePath + ForEach ($File in $SourceFilePath) { + # Copy the files and folders from the source directory to the archive + $null = $Archive.CopyHere($File.FullName) + # Wait for archive operation to complete. Archive file count property returns 0 if archive operation is in progress. + Write-Log -Message "Compressing file [$($File.FullName)] to destination path [$DestinationPath]..." -Source ${CmdletName} + Do { Start-Sleep -Milliseconds 250 } While ($Archive.Items().Count -eq 0) + + # If option was selected, delete the source file after successfully archiving the content + If ($RemoveSourceAfterArchiving) { + Try { + Write-Log -Message "Delete the source file [$($File.FullName)] as it has been successfully archived." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $File.FullName -Force -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to delete the source file [$($File.FullName)]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + } + } + + ## If the archive was created in session 0 or by an Admin, then it may only be readable by elevated users. + # Apply the parent folder's permissions to the archive file to fix the problem. + Write-Log -Message "If the archive was created in session 0 or by an Admin, then it may only be readable by elevated users. Apply permissions from parent folder [$DestinationArchiveDirectoryPath] to file [$DestinationPath]." -Source ${CmdletName} + Try { + [Security.AccessControl.DirectorySecurity]$DestinationArchiveDirectoryPathAcl = Get-Acl -Path $DestinationArchiveDirectoryPath -ErrorAction 'Stop' + Set-Acl -Path $DestinationPath -AclObject $DestinationArchiveDirectoryPathAcl -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to apply parent folder's [$DestinationArchiveDirectoryPath] permissions to file [$DestinationPath]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + Catch { + Write-Log -Message "Failed to archive the requested file(s). `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to archive the requested file(s): $($_.Exception.Message)" + } + } + Finally { + ## Release the ComObject representing the archive + If ($Archive) { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($Archive) } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Exit-Script +Function Exit-Script { +<# +.SYNOPSIS + Exit the script, perform cleanup actions, and pass an exit code to the parent process. +.DESCRIPTION + Always use when exiting the script to ensure cleanup actions are performed. +.PARAMETER ExitCode + The exit code to be passed from the script to the parent process, e.g. SCCM +.EXAMPLE + Exit-Script -ExitCode 0 +.EXAMPLE + Exit-Script -ExitCode 1618 +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$ExitCode = 0 + ) + + ## Get the name of this function + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + ## Stop the Close Program Dialog if running + If ($formCloseApps) { $formCloseApps.Close } + + ## Close the Installation Progress Dialog if running + Close-InstallationProgress + + ## If block execution variable is true, call the function to unblock execution + If ($BlockExecution) { Unblock-AppExecution } + + ## If Terminal Server mode was set, turn it off + If ($terminalServerMode) { Disable-TerminalServerInstallMode } + + ## Determine action based on exit code + Switch ($exitCode) { + $configInstallationUIExitCode { $installSuccess = $false } + $configInstallationDeferExitCode { $installSuccess = $false } + 3010 { $installSuccess = $true } + 1641 { $installSuccess = $true } + 0 { $installSuccess = $true } + Default { $installSuccess = $false } + } + + ## Determine if balloon notification should be shown + If ($deployModeSilent) { [boolean]$configShowBalloonNotifications = $false } + + If ($installSuccess) { + If (Test-Path -LiteralPath $regKeyDeferHistory -ErrorAction 'SilentlyContinue') { + Write-Log -Message 'Remove deferral history...' -Source ${CmdletName} + Remove-RegistryKey -Key $regKeyDeferHistory -Recurse + } + + [string]$balloonText = "$deploymentTypeName $configBalloonTextComplete" + ## Handle reboot prompts on successful script completion + If (($AllowRebootPassThru) -and ((($msiRebootDetected) -or ($exitCode -eq 3010)) -or ($exitCode -eq 1641))) { + Write-Log -Message 'A restart has been flagged as required.' -Source ${CmdletName} + [string]$balloonText = "$deploymentTypeName $configBalloonTextRestartRequired" + If (($msiRebootDetected) -and ($exitCode -ne 1641)) { [int32]$exitCode = 3010 } + } + Else { + [int32]$exitCode = 0 + } + + Write-Log -Message "$installName $deploymentTypeName completed with exit code [$exitcode]." -Source ${CmdletName} + If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Info' -BalloonTipText $balloonText } + } + ElseIf (-not $installSuccess) { + Write-Log -Message "$installName $deploymentTypeName completed with exit code [$exitcode]." -Source ${CmdletName} + If (($exitCode -eq $configInstallationUIExitCode) -or ($exitCode -eq $configInstallationDeferExitCode)) { + [string]$balloonText = "$deploymentTypeName $configBalloonTextFastRetry" + If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Warning' -BalloonTipText $balloonText } + } + Else { + [string]$balloonText = "$deploymentTypeName $configBalloonTextError" + If ($configShowBalloonNotifications) { Show-BalloonTip -BalloonTipIcon 'Error' -BalloonTipText $balloonText } + } + } + + [string]$LogDash = '-' * 79 + Write-Log -Message $LogDash -Source ${CmdletName} + + ## Archive the log files to zip format and then delete the temporary logs folder + If ($configToolkitCompressLogs) { + ## Disable logging to file so that we can archive the log files + . $DisableScriptLogging + + [string]$DestinationArchiveFileName = $installName + '_' + $deploymentType + '_' + ((Get-Date -Format 'yyyy-MM-dd-HH-mm-ss').ToString()) + '.zip' + New-ZipFile -DestinationArchiveDirectoryPath $configToolkitLogDir -DestinationArchiveFileName $DestinationArchiveFileName -SourceDirectory $logTempFolder -RemoveSourceAfterArchiving + } + + If ($script:notifyIcon) { Try { $script:notifyIcon.Dispose() } Catch {} } + ## Reset powershell window title to its previous title + $Host.UI.RawUI.WindowTitle = $oldPSWindowTitle + ## Exit the script, returning the exit code to SCCM + If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $exitCode; Exit } Else { Exit $exitCode } +} +#endregion + + +#region Function Resolve-Error +Function Resolve-Error { +<# +.SYNOPSIS + Enumerate error record details. +.DESCRIPTION + Enumerate an error record, or a collection of error record, properties. By default, the details for the last error will be enumerated. +.PARAMETER ErrorRecord + The error record to resolve. The default error record is the latest one: $global:Error[0]. This parameter will also accept an array of error records. +.PARAMETER Property + The list of properties to display from the error record. Use "*" to display all properties. + Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException +.PARAMETER GetErrorRecord + Get error record details as represented by $_. +.PARAMETER GetErrorInvocation + Get error record invocation information as represented by $_.InvocationInfo. +.PARAMETER GetErrorException + Get error record exception details as represented by $_.Exception. +.PARAMETER GetErrorInnerException + Get error record inner exception details as represented by $_.Exception.InnerException. Will retrieve all inner exceptions if there is more than one. +.EXAMPLE + Resolve-Error +.EXAMPLE + Resolve-Error -Property * +.EXAMPLE + Resolve-Error -Property InnerException +.EXAMPLE + Resolve-Error -GetErrorInvocation:$false +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [AllowEmptyCollection()] + [array]$ErrorRecord, + [Parameter(Mandatory=$false,Position=1)] + [ValidateNotNullorEmpty()] + [string[]]$Property = ('Message','InnerException','FullyQualifiedErrorId','ScriptStackTrace','PositionMessage'), + [Parameter(Mandatory=$false,Position=2)] + [switch]$GetErrorRecord = $true, + [Parameter(Mandatory=$false,Position=3)] + [switch]$GetErrorInvocation = $true, + [Parameter(Mandatory=$false,Position=4)] + [switch]$GetErrorException = $true, + [Parameter(Mandatory=$false,Position=5)] + [switch]$GetErrorInnerException = $true + ) + + Begin { + ## If function was called without specifying an error record, then choose the latest error that occurred + If (-not $ErrorRecord) { + If ($global:Error.Count -eq 0) { + #Write-Warning -Message "The `$Error collection is empty" + Return + } + Else { + [array]$ErrorRecord = $global:Error[0] + } + } + + ## Allows selecting and filtering the properties on the error object if they exist + [scriptblock]$SelectProperty = { + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + $InputObject, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string[]]$Property + ) + + [string[]]$ObjectProperty = $InputObject | Get-Member -MemberType '*Property' | Select-Object -ExpandProperty 'Name' + ForEach ($Prop in $Property) { + If ($Prop -eq '*') { + [string[]]$PropertySelection = $ObjectProperty + Break + } + ElseIf ($ObjectProperty -contains $Prop) { + [string[]]$PropertySelection += $Prop + } + } + Write-Output -InputObject $PropertySelection + } + + # Initialize variables to avoid error if 'Set-StrictMode' is set + $LogErrorRecordMsg = $null + $LogErrorInvocationMsg = $null + $LogErrorExceptionMsg = $null + $LogErrorMessageTmp = $null + $LogInnerMessage = $null + } + Process { + If (-not $ErrorRecord) { Return } + ForEach ($ErrRecord in $ErrorRecord) { + ## Capture Error Record + If ($GetErrorRecord) { + [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord -Property $Property + $LogErrorRecordMsg = $ErrRecord | Select-Object -Property $SelectedProperties + } + + ## Error Invocation Information + If ($GetErrorInvocation) { + If ($ErrRecord.InvocationInfo) { + [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.InvocationInfo -Property $Property + $LogErrorInvocationMsg = $ErrRecord.InvocationInfo | Select-Object -Property $SelectedProperties + } + } + + ## Capture Error Exception + If ($GetErrorException) { + If ($ErrRecord.Exception) { + [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.Exception -Property $Property + $LogErrorExceptionMsg = $ErrRecord.Exception | Select-Object -Property $SelectedProperties + } + } + + ## Display properties in the correct order + If ($Property -eq '*') { + # If all properties were chosen for display, then arrange them in the order the error object displays them by default. + If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg } + If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg } + If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg } + } + Else { + # Display selected properties in our custom order + If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg } + If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg } + If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg } + } + + If ($LogErrorMessageTmp) { + $LogErrorMessage = 'Error Record:' + $LogErrorMessage += "`n-------------" + $LogErrorMsg = $LogErrorMessageTmp | Format-List | Out-String + $LogErrorMessage += $LogErrorMsg + } + + ## Capture Error Inner Exception(s) + If ($GetErrorInnerException) { + If ($ErrRecord.Exception -and $ErrRecord.Exception.InnerException) { + $LogInnerMessage = 'Error Inner Exception(s):' + $LogInnerMessage += "`n-------------------------" + + $ErrorInnerException = $ErrRecord.Exception.InnerException + $Count = 0 + + While ($ErrorInnerException) { + [string]$InnerExceptionSeperator = '~' * 40 + + [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrorInnerException -Property $Property + $LogErrorInnerExceptionMsg = $ErrorInnerException | Select-Object -Property $SelectedProperties | Format-List | Out-String + + If ($Count -gt 0) { $LogInnerMessage += $InnerExceptionSeperator } + $LogInnerMessage += $LogErrorInnerExceptionMsg + + $Count++ + $ErrorInnerException = $ErrorInnerException.InnerException + } + } + } + + If ($LogErrorMessage) { $Output = $LogErrorMessage } + If ($LogInnerMessage) { $Output += $LogInnerMessage } + + Write-Output -InputObject $Output + + If (Test-Path -LiteralPath 'variable:Output') { Clear-Variable -Name 'Output' } + If (Test-Path -LiteralPath 'variable:LogErrorMessage') { Clear-Variable -Name 'LogErrorMessage' } + If (Test-Path -LiteralPath 'variable:LogInnerMessage') { Clear-Variable -Name 'LogInnerMessage' } + If (Test-Path -LiteralPath 'variable:LogErrorMessageTmp') { Clear-Variable -Name 'LogErrorMessageTmp' } + } + } + End { + } +} +#endregion + + +#region Function Show-InstallationPrompt +Function Show-InstallationPrompt { +<# +.SYNOPSIS + Displays a custom installation prompt with the toolkit branding and optional buttons. +.DESCRIPTION + Any combination of Left, Middle or Right buttons can be displayed. The return value of the button clicked by the user is the button text specified. +.PARAMETER Title + Title of the prompt. Default: the application installation name. +.PARAMETER Message + Message text to be included in the prompt +.PARAMETER MessageAlignment + Alignment of the message text. Options: Left, Center, Right. Default: Center. +.PARAMETER ButtonLeftText + Show a button on the left of the prompt with the specified text +.PARAMETER ButtonRightText + Show a button on the right of the prompt with the specified text +.PARAMETER ButtonMiddleText + Show a button in the middle of the prompt with the specified text +.PARAMETER Icon + Show a system icon in the prompt. Options: Application, Asterisk, Error, Exclamation, Hand, Information, None, Question, Shield, Warning, WinLogo. Default: None. +.PARAMETER NoWait + Specifies whether to show the prompt asynchronously (i.e. allow the script to continue without waiting for a response). Default: $false. +.PARAMETER PersistPrompt + Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml. The user will have no option but to respond to the prompt - resistance is futile! +.PARAMETER MinimizeWindows + Specifies whether to minimize other windows when displaying prompt. Default: $false. +.PARAMETER Timeout + Specifies the time period in seconds after which the prompt should timeout. Default: UI timeout value set in the config XML file. +.PARAMETER ExitOnTimeout + Specifies whether to exit the script if the UI times out. Default: $true. +.EXAMPLE + Show-InstallationPrompt -Message 'Do you want to proceed with the installation?' -ButtonRightText 'Yes' -ButtonLeftText 'No' +.EXAMPLE + Show-InstallationPrompt -Title 'Funny Prompt' -Message 'How are you feeling today?' -ButtonRightText 'Good' -ButtonLeftText 'Bad' -ButtonMiddleText 'Indifferent' +.EXAMPLE + Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install, or remove it completely for unattended installations.' -Icon Information -NoWait +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Title = $installTitle, + [Parameter(Mandatory=$false)] + [string]$Message = '', + [Parameter(Mandatory=$false)] + [ValidateSet('Left','Center','Right')] + [string]$MessageAlignment = 'Center', + [Parameter(Mandatory=$false)] + [string]$ButtonRightText = '', + [Parameter(Mandatory=$false)] + [string]$ButtonLeftText = '', + [Parameter(Mandatory=$false)] + [string]$ButtonMiddleText = '', + [Parameter(Mandatory=$false)] + [ValidateSet('Application','Asterisk','Error','Exclamation','Hand','Information','None','Question','Shield','Warning','WinLogo')] + [string]$Icon = 'None', + [Parameter(Mandatory=$false)] + [switch]$NoWait = $false, + [Parameter(Mandatory=$false)] + [switch]$PersistPrompt = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$MinimizeWindows = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$Timeout = $configInstallationUITimeout, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ExitOnTimeout = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Bypass if in non-interactive mode + If ($deployModeSilent) { + Write-Log -Message "Bypassing Installation Prompt [Mode: $deployMode]... $Message" -Source ${CmdletName} + Return + } + + ## Get parameters for calling function asynchronously + [hashtable]$installPromptParameters = $psBoundParameters + + ## Check if the countdown was specified + If ($timeout -gt $configInstallationUITimeout) { + [string]$CountdownTimeoutErr = "The installation UI dialog timeout cannot be longer than the timeout specified in the XML configuration file." + Write-Log -Message $CountdownTimeoutErr -Severity 3 -Source ${CmdletName} + Throw $CountdownTimeoutErr + } + + [Windows.Forms.Application]::EnableVisualStyles() + $formInstallationPrompt = New-Object -TypeName 'System.Windows.Forms.Form' + $pictureBanner = New-Object -TypeName 'System.Windows.Forms.PictureBox' + If ($Icon -ne 'None') { + $pictureIcon = New-Object -TypeName 'System.Windows.Forms.PictureBox' + } + $labelText = New-Object -TypeName 'System.Windows.Forms.Label' + $buttonRight = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonMiddle = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonLeft = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonAbort = New-Object -TypeName 'System.Windows.Forms.Button' + $flowLayoutPanel = New-Object -TypeName 'System.Windows.Forms.FlowLayoutPanel' + $panelButtons = New-Object -TypeName 'System.Windows.Forms.Panel' + $InitialFormInstallationPromptWindowState = New-Object -TypeName 'System.Windows.Forms.FormWindowState' + + [scriptblock]$Form_Cleanup_FormClosed = { + ## Remove all event handlers from the controls + Try { + $labelText.remove_Click($handler_labelText_Click) + $buttonLeft.remove_Click($buttonLeft_OnClick) + $buttonRight.remove_Click($buttonRight_OnClick) + $buttonMiddle.remove_Click($buttonMiddle_OnClick) + $buttonAbort.remove_Click($buttonAbort_OnClick) + $timer.remove_Tick($timer_Tick) + $timer.Dispose() + $timer = $null + $timerPersist.remove_Tick($timerPersist_Tick) + $timerPersist.Dispose() + $timerPersist = $null + $formInstallationPrompt.remove_Load($Form_StateCorrection_Load) + $formInstallationPrompt.remove_FormClosed($Form_Cleanup_FormClosed) + } + Catch { } + } + + [scriptblock]$Form_StateCorrection_Load = { + ## Correct the initial state of the form to prevent the .NET maximized form issue + $formInstallationPrompt.WindowState = 'Normal' + $formInstallationPrompt.AutoSize = $true + $formInstallationPrompt.TopMost = $true + $formInstallationPrompt.BringToFront() + # Get the start position of the form so we can return the form to this position if PersistPrompt is enabled + Set-Variable -Name 'formInstallationPromptStartPosition' -Value $formInstallationPrompt.Location -Scope 'Script' + } + + ## Form + $formInstallationPrompt.Controls.Add($pictureBanner) + + ##---------------------------------------------- + ## Create padding object + $paddingNone = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,0,0,0 + + ## Default control size + $DefaultControlSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,0 + + ## Generic Button properties + $buttonSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 110,24 + + ## Picture Banner + $pictureBanner.DataBindings.DefaultDataSourceUpdateMode = 0 + $pictureBanner.ImageLocation = $appDeployLogoBanner + $pictureBanner.Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,$appDeployLogoBannerHeight + $pictureBanner.MinimumSize = $DefaultControlSize + $pictureBanner.SizeMode = 'CenterImage' + $pictureBanner.Margin = $paddingNone + $pictureBanner.TabIndex = 0 + $pictureBanner.TabStop = $false + $pictureBanner.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,0 + + ## Picture Icon + If ($Icon -ne 'None') { + $pictureIcon.DataBindings.DefaultDataSourceUpdateMode = 0 + $pictureIcon.Image = ([Drawing.SystemIcons]::$Icon).ToBitmap() + $pictureIcon.Name = 'pictureIcon' + $pictureIcon.MinimumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 64,32 + $pictureIcon.Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 64,32 + $pictureIcon.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 24,0,8,0 + $pictureIcon.SizeMode = "CenterImage" + $pictureIcon.TabIndex = 0 + $pictureIcon.TabStop = $false + $pictureIcon.Anchor = 'None' + $pictureIcon.Margin = $paddingNone + } + + ## Label Text + $labelText.DataBindings.DefaultDataSourceUpdateMode = 0 + $labelText.Name = 'labelText' + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' 386,0 + $labelText.Size = $System_Drawing_Size + $labelText.MinimumSize = $System_Drawing_Size + $labelText.MaximumSize = $System_Drawing_Size + $labelText.AutoSize = $true + $labelText.Margin = $paddingNone + $labelText.TabIndex = 1 + $labelText.Text = $message + $labelText.TextAlign = "Middle$($MessageAlignment)" + $labelText.Anchor = 'None' + $labelText.add_Click($handler_labelText_Click) + + If ($Icon -ne 'None') { + # Add margin for the icon based on labelText Height so its centered + $pictureIcon.Height = $labelText.Height + } + ## Button Left + $buttonLeft.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonLeft.Name = 'buttonLeft' + $buttonLeft.Size = $buttonSize + $buttonLeft.TabIndex = 5 + $buttonLeft.Text = $buttonLeftText + $buttonLeft.DialogResult = 'No' + $buttonLeft.AutoSize = $false + $buttonLeft.UseVisualStyleBackColor = $true + $buttonLeft.Location = "15,5" + $buttonLeft.add_Click($buttonLeft_OnClick) + + ## Button Middle + $buttonMiddle.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonMiddle.Name = 'buttonMiddle' + $buttonMiddle.Size = $buttonSize + $buttonMiddle.TabIndex = 6 + $buttonMiddle.Text = $buttonMiddleText + $buttonMiddle.DialogResult = 'Ignore' + $buttonMiddle.AutoSize = $true + $buttonMiddle.UseVisualStyleBackColor = $true + $buttonMiddle.Location = "170,5" + $buttonMiddle.add_Click($buttonMiddle_OnClick) + + ## Button Right + $buttonRight.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonRight.Name = 'buttonRight' + $buttonRight.Size = $buttonSize + $buttonRight.TabIndex = 7 + $buttonRight.Text = $ButtonRightText + $buttonRight.DialogResult = 'Yes' + $buttonRight.AutoSize = $true + $buttonRight.UseVisualStyleBackColor = $true + $buttonRight.Location = "325,5" + $buttonRight.add_Click($buttonRight_OnClick) + + ## Button Abort (Hidden) + $buttonAbort.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonAbort.Name = 'buttonAbort' + $buttonAbort.Size = '1,1' + $buttonAbort.DialogResult = 'Abort' + $buttonAbort.TabStop = $false + $buttonAbort.Visible = $false + $buttonAbort.UseVisualStyleBackColor = $true + $buttonAbort.Location = "0,0" + $buttonAbort.add_Click($buttonAbort_OnClick) + + ## FlowLayoutPanel + $flowLayoutPanel.MinimumSize = $DefaultControlSize + $flowLayoutPanel.MaximumSize = $DefaultControlSize + $flowLayoutPanel.Size = $DefaultControlSize + $flowLayoutPanel.AutoSize = $true + $flowLayoutPanel.Anchor = 'Top,Left' + $flowLayoutPanel.FlowDirection = 'LeftToRight' + $flowLayoutPanel.WrapContents = $true + $flowLayoutPanel.Margin = $paddingNone + If ($Icon -ne 'None') { + $flowLayoutPanel.Controls.Add($pictureIcon) + } + $flowLayoutPanel.Controls.Add($labelText) + ## Make sure label text is positioned correctly + If ($Icon -ne 'None') { + $labelText.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,5,10,5 + $pictureIcon.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,0 + $labelText.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 64,0 + } else { + $labelText.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 10,5,10,5 + $labelText.MinimumSize = $DefaultControlSize + $labelText.MaximumSize = $DefaultControlSize + $labelText.Size = $DefaultControlSize + $labelText.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,0 + } + $flowLayoutPanel.Controls.Add($buttonAbort) + $flowLayoutPanel.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,$appDeployLogoBannerHeight + + ## ButtonsPanel + $panelButtons.MinimumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.Padding = $paddingNone + $panelButtons.Margin = $paddingNone + $panelButtons.MaximumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.AutoSize = $true + If ($buttonLeftText) { $panelButtons.Controls.Add($buttonLeft) } + If ($buttonMiddleText) { $panelButtons.Controls.Add($buttonMiddle) } + If ($buttonRightText) { $panelButtons.Controls.Add($buttonRight) } + ## Add the ButtonsPanel to the flowLayoutPanel if any buttons are present + If ($buttonLeftText -or $buttonMiddleText -or $buttonRightText) { + $flowLayoutPanel.Controls.Add($panelButtons) + } + + ## Form Installation Prompt + $formInstallationPrompt.MinimumSize = $DefaultControlSize + $formInstallationPrompt.Size = $DefaultControlSize + $formInstallationPrompt.Padding = $paddingNone + $formInstallationPrompt.Margin = $paddingNone + $formInstallationPrompt.DataBindings.DefaultDataSourceUpdateMode = 0 + $formInstallationPrompt.Name = 'InstallPromptForm' + $formInstallationPrompt.Text = $title + $formInstallationPrompt.StartPosition = 'CenterScreen' + $formInstallationPrompt.FormBorderStyle = 'FixedDialog' + $formInstallationPrompt.MaximizeBox = $false + $formInstallationPrompt.MinimizeBox = $false + $formInstallationPrompt.TopMost = $true + $formInstallationPrompt.TopLevel = $true + $formInstallationPrompt.AutoSize = $true + $formInstallationPrompt.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + $formInstallationPrompt.Controls.Add($pictureBanner) + $formInstallationPrompt.Controls.Add($flowLayoutPanel) + ## Timer + $timer = New-Object -TypeName 'System.Windows.Forms.Timer' + $timer.Interval = ($timeout * 1000) + $timer.Add_Tick({ + Write-Log -Message 'Installation action not taken within a reasonable amount of time.' -Source ${CmdletName} + $buttonAbort.PerformClick() + }) + + ## Save the initial state of the form + $InitialFormInstallationPromptWindowState = $formInstallationPrompt.WindowState + ## Init the OnLoad event to correct the initial state of the form + $formInstallationPrompt.add_Load($Form_StateCorrection_Load) + ## Clean up the control events + $formInstallationPrompt.add_FormClosed($Form_Cleanup_FormClosed) + + ## Start the timer + $timer.Start() + + ## Persistence Timer + [scriptblock]$RefreshInstallationPrompt = { + $formInstallationPrompt.BringToFront() + $formInstallationPrompt.Location = "$($formInstallationPromptStartPosition.X),$($formInstallationPromptStartPosition.Y)" + $formInstallationPrompt.Refresh() + } + If ($persistPrompt) { + $timerPersist = New-Object -TypeName 'System.Windows.Forms.Timer' + $timerPersist.Interval = ($configInstallationPersistInterval * 1000) + [scriptblock]$timerPersist_Tick = { & $RefreshInstallationPrompt } + $timerPersist.add_Tick($timerPersist_Tick) + $timerPersist.Start() + } + + ## Close the Installation Progress Dialog if running + Close-InstallationProgress + + [string]$installPromptLoggedParameters = ($installPromptParameters.GetEnumerator() | ForEach-Object { If ($_.Value.GetType().Name -eq 'SwitchParameter') { "-$($_.Key):`$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Boolean') { "-$($_.Key) `$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Int32') { "-$($_.Key) $($_.Value)" } Else { "-$($_.Key) `"$($_.Value)`"" } }) -join ' ' + Write-Log -Message "Displaying custom installation prompt with the non-default parameters: [$installPromptLoggedParameters]." -Source ${CmdletName} + + ## If the NoWait parameter is specified, launch a new PowerShell session to show the prompt asynchronously + If ($NoWait) { + # Remove the NoWait parameter so that the script is run synchronously in the new PowerShell session + $installPromptParameters.Remove('NoWait') + # Format the parameters as a string + [string]$installPromptParameters = ($installPromptParameters.GetEnumerator() | ForEach-Object { If ($_.Value.GetType().Name -eq 'SwitchParameter') { "-$($_.Key):`$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Boolean') { "-$($_.Key) `$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Int32') { "-$($_.Key) $($_.Value)" } Else { "-$($_.Key) `"$($_.Value)`"" } }) -join ' ' + Start-Process -FilePath "$PSHOME\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -File `"$scriptPath`" -ReferredInstallTitle `"$Title`" -ReferredInstallName `"$installName`" -ReferredLogName `"$logName`" -ShowInstallationPrompt $installPromptParameters -AsyncToolkitLaunch" -WindowStyle 'Hidden' -ErrorAction 'SilentlyContinue' + } + ## Otherwise, show the prompt synchronously. If user cancels, then keep showing it until user responds using one of the buttons. + Else { + $showDialog = $true + While ($showDialog) { + # Minimize all other windows + If ($minimizeWindows) { $null = $shellApp.MinimizeAll() } + # Show the Form + $result = $formInstallationPrompt.ShowDialog() + If (($result -eq 'Yes') -or ($result -eq 'No') -or ($result -eq 'Ignore') -or ($result -eq 'Abort')) { + $showDialog = $false + } + } + $formInstallationPrompt.Dispose() + + Switch ($result) { + 'Yes' { Write-Output -InputObject $buttonRightText } + 'No' { Write-Output -InputObject $buttonLeftText } + 'Ignore' { Write-Output -InputObject $buttonMiddleText } + 'Abort' { + # Restore minimized windows + $null = $shellApp.UndoMinimizeAll() + If ($ExitOnTimeout) { + Exit-Script -ExitCode $configInstallationUIExitCode + } + Else { + Write-Log -Message 'UI timed out but `$ExitOnTimeout set to `$false. Continue...' -Source ${CmdletName} + } + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-DialogBox +Function Show-DialogBox { +<# +.SYNOPSIS + Display a custom dialog box with optional title, buttons, icon and timeout. + Show-InstallationPrompt is recommended over this function as it provides more customization and uses consistent branding with the other UI components. +.DESCRIPTION + Display a custom dialog box with optional title, buttons, icon and timeout. The default button is "OK", the default Icon is "None", and the default Timeout is none. +.PARAMETER Text + Text in the message dialog box +.PARAMETER Title + Title of the message dialog box +.PARAMETER Buttons + Buttons to be included on the dialog box. Options: OK, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel, CancelTryAgainContinue. Default: OK. +.PARAMETER DefaultButton + The Default button that is selected. Options: First, Second, Third. Default: First. +.PARAMETER Icon + Icon to display on the dialog box. Options: None, Stop, Question, Exclamation, Information. Default: None. +.PARAMETER Timeout + Timeout period in seconds before automatically closing the dialog box with the return message "Timeout". Default: UI timeout value set in the config XML file. +.PARAMETER TopMost + Specifies whether the message box is a system modal message box and appears in a topmost window. Default: $true. +.EXAMPLE + Show-DialogBox -Title 'Installed Complete' -Text 'Installation has completed. Please click OK and restart your computer.' -Icon 'Information' +.EXAMPLE + Show-DialogBox -Title 'Installation Notice' -Text 'Installation will take approximately 30 minutes. Do you wish to proceed?' -Buttons 'OKCancel' -DefaultButton 'Second' -Icon 'Exclamation' -Timeout 600 -Topmost $false +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0,HelpMessage='Enter a message for the dialog box')] + [ValidateNotNullorEmpty()] + [string]$Text, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Title = $installTitle, + [Parameter(Mandatory=$false)] + [ValidateSet('OK','OKCancel','AbortRetryIgnore','YesNoCancel','YesNo','RetryCancel','CancelTryAgainContinue')] + [string]$Buttons = 'OK', + [Parameter(Mandatory=$false)] + [ValidateSet('First','Second','Third')] + [string]$DefaultButton = 'First', + [Parameter(Mandatory=$false)] + [ValidateSet('Exclamation','Information','None','Stop','Question')] + [string]$Icon = 'None', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Timeout = $configInstallationUITimeout, + [Parameter(Mandatory=$false)] + [boolean]$TopMost = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + # Bypass if in non-interactive mode + If ($deployModeNonInteractive) { + Write-Log -Message "Bypassing Dialog Box [Mode: $deployMode]: $Text..." -Source ${CmdletName} + Return + } + + Write-Log -Message "Display Dialog Box with message: $Text..." -Source ${CmdletName} + + [hashtable]$dialogButtons = @{ + 'OK' = 0 + 'OKCancel' = 1 + 'AbortRetryIgnore' = 2 + 'YesNoCancel' = 3 + 'YesNo' = 4 + 'RetryCancel' = 5 + 'CancelTryAgainContinue' = 6 + } + + [hashtable]$dialogIcons = @{ + 'None' = 0 + 'Stop' = 16 + 'Question' = 32 + 'Exclamation' = 48 + 'Information' = 64 + } + + [hashtable]$dialogDefaultButton = @{ + 'First' = 0 + 'Second' = 256 + 'Third' = 512 + } + + Switch ($TopMost) { + $true { $dialogTopMost = 4096 } + $false { $dialogTopMost = 0 } + } + + $response = $Shell.Popup($Text, $Timeout, $Title, ($dialogButtons[$Buttons] + $dialogIcons[$Icon] + $dialogDefaultButton[$DefaultButton] + $dialogTopMost)) + + Switch ($response) { + 1 { + Write-Log -Message 'Dialog Box Response: OK' -Source ${CmdletName} + Write-Output -InputObject 'OK' + } + 2 { + Write-Log -Message 'Dialog Box Response: Cancel' -Source ${CmdletName} + Write-Output -InputObject 'Cancel' + } + 3 { + Write-Log -Message 'Dialog Box Response: Abort' -Source ${CmdletName} + Write-Output -InputObject 'Abort' + } + 4 { + Write-Log -Message 'Dialog Box Response: Retry' -Source ${CmdletName} + Write-Output -InputObject 'Retry' + } + 5 { + Write-Log -Message 'Dialog Box Response: Ignore' -Source ${CmdletName} + Write-Output -InputObject 'Ignore' + } + 6 { + Write-Log -Message 'Dialog Box Response: Yes' -Source ${CmdletName} + Write-Output -InputObject 'Yes' + } + 7 { + Write-Log -Message 'Dialog Box Response: No' -Source ${CmdletName} + Write-Output -InputObject 'No' + } + 10 { + Write-Log -Message 'Dialog Box Response: Try Again' -Source ${CmdletName} + Write-Output -InputObject 'Try Again' + } + 11 { + Write-Log -Message 'Dialog Box Response: Continue' -Source ${CmdletName} + Write-Output -InputObject 'Continue' + } + -1 { + Write-Log -Message 'Dialog Box Timed Out...' -Source ${CmdletName} + Write-Output -InputObject 'Timeout' + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-HardwarePlatform +Function Get-HardwarePlatform { +<# +.SYNOPSIS + Retrieves information about the hardware platform (physical or virtual) +.DESCRIPTION + Retrieves information about the hardware platform (physical or virtual) +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-HardwarePlatform +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Retrieve hardware platform information.' -Source ${CmdletName} + $hwBios = Get-WmiObject -Class 'Win32_BIOS' -ErrorAction 'Stop' | Select-Object -Property 'Version', 'SerialNumber' + $hwMakeModel = Get-WMIObject -Class 'Win32_ComputerSystem' -ErrorAction 'Stop' | Select-Object -Property 'Model', 'Manufacturer' + + If ($hwBIOS.Version -match 'VRTUAL') { $hwType = 'Virtual:Hyper-V' } + ElseIf ($hwBIOS.Version -match 'A M I') { $hwType = 'Virtual:Virtual PC' } + ElseIf ($hwBIOS.Version -like '*Xen*') { $hwType = 'Virtual:Xen' } + ElseIf ($hwBIOS.SerialNumber -like '*VMware*') { $hwType = 'Virtual:VMWare' } + ElseIf (($hwMakeModel.Manufacturer -like '*Microsoft*') -and ($hwMakeModel.Model -notlike '*Surface*')) { $hwType = 'Virtual:Hyper-V' } + ElseIf ($hwMakeModel.Manufacturer -like '*VMWare*') { $hwType = 'Virtual:VMWare' } + ElseIf ($hwMakeModel.Model -like '*Virtual*') { $hwType = 'Virtual' } + Else { $hwType = 'Physical' } + + Write-Output -InputObject $hwType + } + Catch { + Write-Log -Message "Failed to retrieve hardware platform information. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to retrieve hardware platform information: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-FreeDiskSpace +Function Get-FreeDiskSpace { +<# +.SYNOPSIS + Retrieves the free disk space in MB on a particular drive (defaults to system drive) +.DESCRIPTION + Retrieves the free disk space in MB on a particular drive (defaults to system drive) +.PARAMETER Drive + Drive to check free disk space on +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-FreeDiskSpace -Drive 'C:' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Drive = $envSystemDrive, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Retrieve free disk space for drive [$Drive]." -Source ${CmdletName} + $disk = Get-WmiObject -Class 'Win32_LogicalDisk' -Filter "DeviceID='$Drive'" -ErrorAction 'Stop' + [double]$freeDiskSpace = [math]::Round($disk.FreeSpace / 1MB) + + Write-Log -Message "Free disk space for drive [$Drive]: [$freeDiskSpace MB]." -Source ${CmdletName} + Write-Output -InputObject $freeDiskSpace + } + Catch { + Write-Log -Message "Failed to retrieve free disk space for drive [$Drive]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to retrieve free disk space for drive [$Drive]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-InstalledApplication +Function Get-InstalledApplication { +<# +.SYNOPSIS + Retrieves information about installed applications. +.DESCRIPTION + Retrieves information about installed applications by querying the registry. You can specify an application name, a product code, or both. + Returns information about application publisher, name & version, product code, uninstall string, install source, location, date, and application architecture. +.PARAMETER Name + The name of the application to retrieve information for. Performs a contains match on the application display name by default. +.PARAMETER Exact + Specifies that the named application must be matched using the exact name. +.PARAMETER WildCard + Specifies that the named application must be matched using a wildcard search. +.PARAMETER RegEx + Specifies that the named application must be matched using a regular expression search. +.PARAMETER ProductCode + The product code of the application to retrieve information for. +.PARAMETER IncludeUpdatesAndHotfixes + Include matches against updates and hotfixes in results. +.EXAMPLE + Get-InstalledApplication -Name 'Adobe Flash' +.EXAMPLE + Get-InstalledApplication -ProductCode '{1AD147D0-BE0E-3D6C-AC11-64F6DC4163F1}' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string[]]$Name, + [Parameter(Mandatory=$false)] + [switch]$Exact = $false, + [Parameter(Mandatory=$false)] + [switch]$WildCard = $false, + [Parameter(Mandatory=$false)] + [switch]$RegEx = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$ProductCode, + [Parameter(Mandatory=$false)] + [switch]$IncludeUpdatesAndHotfixes + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If ($name) { + Write-Log -Message "Get information for installed Application Name(s) [$($name -join ', ')]..." -Source ${CmdletName} + } + If ($productCode) { + Write-Log -Message "Get information for installed Product Code [$ProductCode]..." -Source ${CmdletName} + } + + ## Enumerate the installed applications from the registry for applications that have the "DisplayName" property + [psobject[]]$regKeyApplication = @() + ForEach ($regKey in $regKeyApplications) { + If (Test-Path -LiteralPath $regKey -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorUninstallKeyPath') { + [psobject[]]$UninstallKeyApps = Get-ChildItem -LiteralPath $regKey -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorUninstallKeyPath' + ForEach ($UninstallKeyApp in $UninstallKeyApps) { + Try { + [psobject]$regKeyApplicationProps = Get-ItemProperty -LiteralPath $UninstallKeyApp.PSPath -ErrorAction 'Stop' + If ($regKeyApplicationProps.DisplayName) { [psobject[]]$regKeyApplication += $regKeyApplicationProps } + } + Catch{ + Write-Log -Message "Unable to enumerate properties from registry key path [$($UninstallKeyApp.PSPath)]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + Continue + } + } + } + } + If ($ErrorUninstallKeyPath) { + Write-Log -Message "The following error(s) took place while enumerating installed applications from the registry. `n$(Resolve-Error -ErrorRecord $ErrorUninstallKeyPath)" -Severity 2 -Source ${CmdletName} + } + + $UpdatesSkippedCounter = 0 + ## Create a custom object with the desired properties for the installed applications and sanitize property details + [psobject[]]$installedApplication = @() + ForEach ($regKeyApp in $regKeyApplication) { + Try { + [string]$appDisplayName = '' + [string]$appDisplayVersion = '' + [string]$appPublisher = '' + + ## Bypass any updates or hotfixes + If ((-not $IncludeUpdatesAndHotfixes) -and (($regKeyApp.DisplayName -match '(?i)kb\d+') -or ($regKeyApp.DisplayName -match 'Cumulative Update') -or ($regKeyApp.DisplayName -match 'Security Update') -or ($regKeyApp.DisplayName -match 'Hotfix'))) { + $UpdatesSkippedCounter += 1 + Continue + } + + ## Remove any control characters which may interfere with logging and creating file path names from these variables + $appDisplayName = $regKeyApp.DisplayName -replace '[^\u001F-\u007F]','' + $appDisplayVersion = $regKeyApp.DisplayVersion -replace '[^\u001F-\u007F]','' + $appPublisher = $regKeyApp.Publisher -replace '[^\u001F-\u007F]','' + + + ## Determine if application is a 64-bit application + [boolean]$Is64BitApp = If (($is64Bit) -and ($regKeyApp.PSPath -notmatch '^Microsoft\.PowerShell\.Core\\Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node')) { $true } Else { $false } + + If ($ProductCode) { + ## Verify if there is a match with the product code passed to the script + If ($regKeyApp.PSChildName -match [regex]::Escape($productCode)) { + Write-Log -Message "Found installed application [$appDisplayName] version [$appDisplayVersion] matching product code [$productCode]." -Source ${CmdletName} + $installedApplication += New-Object -TypeName 'PSObject' -Property @{ + UninstallSubkey = $regKeyApp.PSChildName + ProductCode = If ($regKeyApp.PSChildName -match $MSIProductCodeRegExPattern) { $regKeyApp.PSChildName } Else { [string]::Empty } + DisplayName = $appDisplayName + DisplayVersion = $appDisplayVersion + UninstallString = $regKeyApp.UninstallString + InstallSource = $regKeyApp.InstallSource + InstallLocation = $regKeyApp.InstallLocation + InstallDate = $regKeyApp.InstallDate + Publisher = $appPublisher + Is64BitApplication = $Is64BitApp + } + } + } + + If ($name) { + ## Verify if there is a match with the application name(s) passed to the script + ForEach ($application in $Name) { + $applicationMatched = $false + If ($exact) { + # Check for an exact application name match + If ($regKeyApp.DisplayName -eq $application) { + $applicationMatched = $true + Write-Log -Message "Found installed application [$appDisplayName] version [$appDisplayVersion] using exact name matching for search term [$application]." -Source ${CmdletName} + } + } + ElseIf ($WildCard) { + # Check for wildcard application name match + If ($regKeyApp.DisplayName -like $application) { + $applicationMatched = $true + Write-Log -Message "Found installed application [$appDisplayName] version [$appDisplayVersion] using wildcard matching for search term [$application]." -Source ${CmdletName} + } + } + ElseIf ($RegEx) { + # Check for a regex application name match + If ($regKeyApp.DisplayName -match $application) { + $applicationMatched = $true + Write-Log -Message "Found installed application [$appDisplayName] version [$appDisplayVersion] using regex matching for search term [$application]." -Source ${CmdletName} + } + } + # Check for a contains application name match + ElseIf ($regKeyApp.DisplayName -match [regex]::Escape($application)) { + $applicationMatched = $true + Write-Log -Message "Found installed application [$appDisplayName] version [$appDisplayVersion] using contains matching for search term [$application]." -Source ${CmdletName} + } + + If ($applicationMatched) { + $installedApplication += New-Object -TypeName 'PSObject' -Property @{ + UninstallSubkey = $regKeyApp.PSChildName + ProductCode = If ($regKeyApp.PSChildName -match $MSIProductCodeRegExPattern) { $regKeyApp.PSChildName } Else { [string]::Empty } + DisplayName = $appDisplayName + DisplayVersion = $appDisplayVersion + UninstallString = $regKeyApp.UninstallString + InstallSource = $regKeyApp.InstallSource + InstallLocation = $regKeyApp.InstallLocation + InstallDate = $regKeyApp.InstallDate + Publisher = $appPublisher + Is64BitApplication = $Is64BitApp + } + } + } + } + } + Catch { + Write-Log -Message "Failed to resolve application details from registry for [$appDisplayName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + Continue + } + } + + If (-not $IncludeUpdatesAndHotfixes) { + ## Write to log the number of entries skipped due to them being considered updates + If ($UpdatesSkippedCounter -eq 1) { + Write-Log -Message "Skipped 1 entry while searching, because it was considered a Microsoft update." -Source ${CmdletName} + } else { + Write-Log -Message "Skipped $UpdatesSkippedCounter entries while searching, because they were considered Microsoft updates." -Source ${CmdletName} + } + } + + If (-not $installedApplication) { + Write-Log -Message "Found no application based on the supplied parameters." -Source ${CmdletName} + } + + Write-Output -InputObject $installedApplication + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Execute-MSI +Function Execute-MSI { +<# +.SYNOPSIS + Executes msiexec.exe to perform the following actions for MSI & MSP files and MSI product codes: install, uninstall, patch, repair, active setup. +.DESCRIPTION + Executes msiexec.exe to perform the following actions for MSI & MSP files and MSI product codes: install, uninstall, patch, repair, active setup. + If the -Action parameter is set to "Install" and the MSI is already installed, the function will exit. + Sets default switches to be passed to msiexec based on the preferences in the XML configuration file. + Automatically generates a log file name and creates a verbose log file for all msiexec operations. + Expects the MSI or MSP file to be located in the "Files" sub directory of the App Deploy Toolkit. Expects transform files to be in the same directory as the MSI file. +.PARAMETER Action + The action to perform. Options: Install, Uninstall, Patch, Repair, ActiveSetup. +.PARAMETER Path + The path to the MSI/MSP file or the product code of the installed MSI. +.PARAMETER Transform + The name of the transform file(s) to be applied to the MSI. The transform file is expected to be in the same directory as the MSI file. +.PARAMETER Patch + The name of the patch (msp) file(s) to be applied to the MSI for use with the "Install" action. The patch file is expected to be in the same directory as the MSI file. +.PARAMETER Parameters + Overrides the default parameters specified in the XML configuration file. Install default is: "REBOOT=ReallySuppress /QB!". Uninstall default is: "REBOOT=ReallySuppress /QN". +.PARAMETER AddParameters + Adds to the default parameters specified in the XML configuration file. Install default is: "REBOOT=ReallySuppress /QB!". Uninstall default is: "REBOOT=ReallySuppress /QN". +.PARAMETER SecureParameters + Hides all parameters passed to the MSI or MSP file from the toolkit Log file. +.PARAMETER LoggingOptions + Overrides the default logging options specified in the XML configuration file. Default options are: "/L*v". +.PARAMETER LogName + Overrides the default log file name. The default log file name is generated from the MSI file name. If LogName does not end in .log, it will be automatically appended. + For uninstallations, by default the product code is resolved to the DisplayName and version of the application. +.PARAMETER WorkingDirectory + Overrides the working directory. The working directory is set to the location of the MSI file. +.PARAMETER SkipMSIAlreadyInstalledCheck + Skips the check to determine if the MSI is already installed on the system. Default is: $false. +.PARAMETER IncludeUpdatesAndHotfixes + Include matches against updates and hotfixes in results. +.PARAMETER NoWait + Immediately continue after executing the process. +.PARAMETER PassThru + Returns ExitCode, STDOut, and STDErr output from the process. +.PARAMETER IgnoreExitCodes + List the exit codes to ignore or * to ignore all exit codes. +.PARAMETER PriorityClass + Specifies priority class for the process. Options: Idle, Normal, High, AboveNormal, BelowNormal, RealTime. Default: Normal +.PARAMETER ExitOnProcessFailure + Specifies whether the function should call Exit-Script when the process returns an exit code that is considered an error/failure. Default: $true +.PARAMETER RepairFromSource + Specifies whether we should repair from source. Also rewrites local cache. Default: $false +.PARAMETER ContinueOnError + Continue if an error occured while trying to start the process. Default: $false. +.EXAMPLE + Execute-MSI -Action 'Install' -Path 'Adobe_FlashPlayer_11.2.202.233_x64_EN.msi' + Installs an MSI +.EXAMPLE + Execute-MSI -Action 'Install' -Path 'Adobe_FlashPlayer_11.2.202.233_x64_EN.msi' -Transform 'Adobe_FlashPlayer_11.2.202.233_x64_EN_01.mst' -Parameters '/QN' + Installs an MSI, applying a transform and overriding the default MSI toolkit parameters +.EXAMPLE + [psobject]$ExecuteMSIResult = Execute-MSI -Action 'Install' -Path 'Adobe_FlashPlayer_11.2.202.233_x64_EN.msi' -PassThru + Installs an MSI and stores the result of the execution into a variable by using the -PassThru option +.EXAMPLE + Execute-MSI -Action 'Uninstall' -Path '{26923b43-4d38-484f-9b9e-de460746276c}' + Uninstalls an MSI using a product code +.EXAMPLE + Execute-MSI -Action 'Patch' -Path 'Adobe_Reader_11.0.3_EN.msp' + Installs an MSP +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateSet('Install','Uninstall','Patch','Repair','ActiveSetup')] + [string]$Action = 'Install', + [Parameter(Mandatory=$true,HelpMessage='Please enter either the path to the MSI/MSP file or the ProductCode')] + [ValidateScript({($_ -match $MSIProductCodeRegExPattern) -or ('.msi','.msp' -contains [IO.Path]::GetExtension($_))})] + [Alias('FilePath')] + [string]$Path, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Transform, + [Parameter(Mandatory=$false)] + [Alias('Arguments')] + [ValidateNotNullorEmpty()] + [string]$Parameters, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$AddParameters, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$SecureParameters = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Patch, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$LoggingOptions, + [Parameter(Mandatory=$false)] + [Alias('LogName')] + [string]$private:LogName, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$WorkingDirectory, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$SkipMSIAlreadyInstalledCheck = $false, + [Parameter(Mandatory=$false)] + [switch]$IncludeUpdatesAndHotfixes = $false, + [Parameter(Mandatory=$false)] + [switch]$NoWait = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$PassThru = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$IgnoreExitCodes, + [Parameter(Mandatory=$false)] + [ValidateSet('Idle', 'Normal', 'High', 'AboveNormal', 'BelowNormal', 'RealTime')] + [Diagnostics.ProcessPriorityClass]$PriorityClass = 'Normal', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ExitOnProcessFailure = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$RepairFromSource = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Initialize variable indicating whether $Path variable is a Product Code or not + [boolean]$PathIsProductCode = $false + + ## If the path matches a product code + If ($Path -match $MSIProductCodeRegExPattern) { + # Set variable indicating that $Path variable is a Product Code + [boolean]$PathIsProductCode = $true + + # Resolve the product code to a publisher, application name, and version + Write-Log -Message 'Resolve product code to a publisher, application name, and version.' -Source ${CmdletName} + + If ($IncludeUpdatesAndHotfixes) { + [psobject]$productCodeNameVersion = Get-InstalledApplication -ProductCode $path -IncludeUpdatesAndHotfixes | Select-Object -Property 'Publisher', 'DisplayName', 'DisplayVersion' -First 1 -ErrorAction 'SilentlyContinue' + } + Else { + [psobject]$productCodeNameVersion = Get-InstalledApplication -ProductCode $path | Select-Object -Property 'Publisher', 'DisplayName', 'DisplayVersion' -First 1 -ErrorAction 'SilentlyContinue' + } + + # Build the log file name + If (-not $logName) { + If ($productCodeNameVersion) { + If ($productCodeNameVersion.Publisher) { + $logName = (Remove-InvalidFileNameChars -Name ($productCodeNameVersion.Publisher + '_' + $productCodeNameVersion.DisplayName + '_' + $productCodeNameVersion.DisplayVersion)) -replace ' ','' + } + Else { + $logName = (Remove-InvalidFileNameChars -Name ($productCodeNameVersion.DisplayName + '_' + $productCodeNameVersion.DisplayVersion)) -replace ' ','' + } + } + Else { + # Out of other options, make the Product Code the name of the log file + $logName = $Path + } + } + } + Else { + # Get the log file name without file extension + If (-not $logName) { $logName = ([IO.FileInfo]$path).BaseName } ElseIf ('.log','.txt' -contains [IO.Path]::GetExtension($logName)) { $logName = [IO.Path]::GetFileNameWithoutExtension($logName) } + } + + If ($configToolkitCompressLogs) { + ## Build the log file path + [string]$logPath = Join-Path -Path $logTempFolder -ChildPath $logName + } + Else { + ## Create the Log directory if it doesn't already exist + If (-not (Test-Path -LiteralPath $configMSILogDir -PathType 'Container' -ErrorAction 'SilentlyContinue')) { + $null = New-Item -Path $configMSILogDir -ItemType 'Directory' -ErrorAction 'SilentlyContinue' + } + ## Build the log file path + [string]$logPath = Join-Path -Path $configMSILogDir -ChildPath $logName + } + + ## Set the installation Parameters + If ($deployModeSilent) { + $msiInstallDefaultParams = $configMSISilentParams + $msiUninstallDefaultParams = $configMSISilentParams + } + Else { + $msiInstallDefaultParams = $configMSIInstallParams + $msiUninstallDefaultParams = $configMSIUninstallParams + } + + ## Build the MSI Parameters + Switch ($action) { + 'Install' { $option = '/i'; [string]$msiLogFile = "$logPath" + '_Install'; $msiDefaultParams = $msiInstallDefaultParams } + 'Uninstall' { $option = '/x'; [string]$msiLogFile = "$logPath" + '_Uninstall'; $msiDefaultParams = $msiUninstallDefaultParams } + 'Patch' { $option = '/update'; [string]$msiLogFile = "$logPath" + '_Patch'; $msiDefaultParams = $msiInstallDefaultParams } + 'Repair' { $option = '/f'; If ($RepairFromSource) { $option += "v" } [string]$msiLogFile = "$logPath" + '_Repair'; $msiDefaultParams = $msiInstallDefaultParams } + 'ActiveSetup' { $option = '/fups'; [string]$msiLogFile = "$logPath" + '_ActiveSetup' } + } + + ## Append ".log" to the MSI logfile path and enclose in quotes + If ([IO.Path]::GetExtension($msiLogFile) -ne '.log') { + [string]$msiLogFile = $msiLogFile + '.log' + [string]$msiLogFile = "`"$msiLogFile`"" + } + + ## If the MSI is in the Files directory, set the full path to the MSI + If (Test-Path -LiteralPath (Join-Path -Path $dirFiles -ChildPath $path -ErrorAction 'SilentlyContinue') -PathType 'Leaf' -ErrorAction 'SilentlyContinue') { + [string]$msiFile = Join-Path -Path $dirFiles -ChildPath $path + } + ElseIf (Test-Path -LiteralPath $Path -ErrorAction 'SilentlyContinue') { + [string]$msiFile = (Get-Item -LiteralPath $Path).FullName + } + ElseIf ($PathIsProductCode) { + [string]$msiFile = $Path + } + Else { + Write-Log -Message "Failed to find MSI file [$path]." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to find MSI file [$path]." + } + Continue + } + + ## Set the working directory of the MSI + If ((-not $PathIsProductCode) -and (-not $workingDirectory)) { [string]$workingDirectory = Split-Path -Path $msiFile -Parent } + + ## Enumerate all transforms specified, qualify the full path if possible and enclose in quotes + If ($transform) { + [string[]]$transforms = $transform -split ',' + 0..($transforms.Length - 1) | ForEach-Object { + If (Test-Path -LiteralPath (Join-Path -Path (Split-Path -Path $msiFile -Parent) -ChildPath $transforms[$_]) -PathType 'Leaf') { + $transforms[$_] = Join-Path -Path (Split-Path -Path $msiFile -Parent) -ChildPath $transforms[$_].Replace('.\','') + } + Else { + $transforms[$_] = $transforms[$_] + } + } + [string]$mstFile = "`"$($transforms -join ';')`"" + } + + ## Enumerate all patches specified, qualify the full path if possible and enclose in quotes + If ($patch) { + [string[]]$patches = $patch -split ',' + 0..($patches.Length - 1) | ForEach-Object { + If (Test-Path -LiteralPath (Join-Path -Path (Split-Path -Path $msiFile -Parent) -ChildPath $patches[$_]) -PathType 'Leaf') { + $patches[$_] = Join-Path -Path (Split-Path -Path $msiFile -Parent) -ChildPath $patches[$_].Replace('.\','') + } + Else { + $patches[$_] = $patches[$_] + } + } + [string]$mspFile = "`"$($patches -join ';')`"" + } + + ## Get the ProductCode of the MSI + If ($PathIsProductCode) { + [string]$MSIProductCode = $path + } + ElseIf ([IO.Path]::GetExtension($msiFile) -eq '.msi') { + Try { + [hashtable]$GetMsiTablePropertySplat = @{ Path = $msiFile; Table = 'Property'; ContinueOnError = $false } + If ($transforms) { $GetMsiTablePropertySplat.Add( 'TransformPath', $transforms ) } + [string]$MSIProductCode = Get-MsiTableProperty @GetMsiTablePropertySplat | Select-Object -ExpandProperty 'ProductCode' -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to get the ProductCode from the MSI file. Continue with requested action [$Action]..." -Source ${CmdletName} + } + } + + ## Enclose the MSI file in quotes to avoid issues with spaces when running msiexec + [string]$msiFile = "`"$msiFile`"" + + ## Start building the MsiExec command line starting with the base action and file + [string]$argsMSI = "$option $msiFile" + # Add MST + If ($transform) { $argsMSI = "$argsMSI TRANSFORMS=$mstFile TRANSFORMSSECURE=1" } + # Add MSP + If ($patch) { $argsMSI = "$argsMSI PATCH=$mspFile" } + # Replace default parameters if specified. + If ($Parameters) { $argsMSI = "$argsMSI $Parameters" } Else { $argsMSI = "$argsMSI $msiDefaultParams" } + # Add reinstallmode and reinstall variable for Patch + If ($action -eq 'Patch') {$argsMSI += " REINSTALLMODE=ecmus REINSTALL=ALL"} + # Append parameters to default parameters if specified. + If ($AddParameters) { $argsMSI = "$argsMSI $AddParameters" } + # Add custom Logging Options if specified, otherwise, add default Logging Options from Config file + If ($LoggingOptions) { $argsMSI = "$argsMSI $LoggingOptions $msiLogFile" } Else { $argsMSI = "$argsMSI $configMSILoggingOptions $msiLogFile" } + + ## Check if the MSI is already installed. If no valid ProductCode to check, then continue with requested MSI action. + If ($MSIProductCode) { + If ($SkipMSIAlreadyInstalledCheck) { + [boolean]$IsMsiInstalled = $false + } + Else { + If ($IncludeUpdatesAndHotfixes) { + [psobject]$MsiInstalled = Get-InstalledApplication -ProductCode $MSIProductCode -IncludeUpdatesAndHotfixes + } + Else { + [psobject]$MsiInstalled = Get-InstalledApplication -ProductCode $MSIProductCode + } + If ($MsiInstalled) { [boolean]$IsMsiInstalled = $true } + } + } + Else { + If ($Action -eq 'Install') { [boolean]$IsMsiInstalled = $false } Else { [boolean]$IsMsiInstalled = $true } + } + + If (($IsMsiInstalled) -and ($Action -eq 'Install')) { + Write-Log -Message "The MSI is already installed on this system. Skipping action [$Action]..." -Source ${CmdletName} + } + ElseIf (((-not $IsMsiInstalled) -and ($Action -eq 'Install')) -or ($IsMsiInstalled)) { + Write-Log -Message "Executing MSI action [$Action]..." -Source ${CmdletName} + # Build the hashtable with the options that will be passed to Execute-Process using splatting + [hashtable]$ExecuteProcessSplat = @{ Path = $exeMsiexec + Parameters = $argsMSI + WindowStyle = 'Normal' } + If ($WorkingDirectory) { $ExecuteProcessSplat.Add( 'WorkingDirectory', $WorkingDirectory) } + If ($ContinueOnError) { $ExecuteProcessSplat.Add( 'ContinueOnError', $ContinueOnError) } + If ($SecureParameters) { $ExecuteProcessSplat.Add( 'SecureParameters', $SecureParameters) } + If ($PassThru) { $ExecuteProcessSplat.Add( 'PassThru', $PassThru) } + If ($IgnoreExitCodes) { $ExecuteProcessSplat.Add( 'IgnoreExitCodes', $IgnoreExitCodes) } + If ($PriorityClass) { $ExecuteProcessSplat.Add( 'PriorityClass', $PriorityClass) } + If ($ExitOnProcessFailure) { $ExecuteProcessSplat.Add( 'ExitOnProcessFailure', $ExitOnProcessFailure) } + If ($NoWait) { $ExecuteProcessSplat.Add( 'NoWait', $NoWait) } + # Call the Execute-Process function + If ($PassThru) { + [psobject]$ExecuteResults = Execute-Process @ExecuteProcessSplat + } + Else { + Execute-Process @ExecuteProcessSplat + } + # Refresh environment variables for Windows Explorer process as Windows does not consistently update environment variables created by MSIs + Update-Desktop + } + Else { + Write-Log -Message "The MSI is not installed on this system. Skipping action [$Action]..." -Source ${CmdletName} + } + } + End { + If ($PassThru) { Write-Output -InputObject $ExecuteResults } + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Remove-MSIApplications +Function Remove-MSIApplications { +<# +.SYNOPSIS + Removes all MSI applications matching the specified application name. +.DESCRIPTION + Removes all MSI applications matching the specified application name. + Enumerates the registry for installed applications matching the specified application name and uninstalls that application using the product code, provided the uninstall string matches "msiexec". +.PARAMETER Name + The name of the application to uninstall. Performs a contains match on the application display name by default. +.PARAMETER Exact + Specifies that the named application must be matched using the exact name. +.PARAMETER WildCard + Specifies that the named application must be matched using a wildcard search. +.PARAMETER Parameters + Overrides the default parameters specified in the XML configuration file. Uninstall default is: "REBOOT=ReallySuppress /QN". +.PARAMETER AddParameters + Adds to the default parameters specified in the XML configuration file. Uninstall default is: "REBOOT=ReallySuppress /QN". +.PARAMETER FilterApplication + Two-dimensional array that contains one or more (property, value, match-type) sets that should be used to filter the list of results returned by Get-InstalledApplication to only those that should be uninstalled. + Properties that can be filtered upon: ProductCode, DisplayName, DisplayVersion, UninstallString, InstallSource, InstallLocation, InstallDate, Publisher, Is64BitApplication +.PARAMETER ExcludeFromUninstall + Two-dimensional array that contains one or more (property, value, match-type) sets that should be excluded from uninstall if found. + Properties that can be excluded: ProductCode, DisplayName, DisplayVersion, UninstallString, InstallSource, InstallLocation, InstallDate, Publisher, Is64BitApplication +.PARAMETER IncludeUpdatesAndHotfixes + Include matches against updates and hotfixes in results. +.PARAMETER LoggingOptions + Overrides the default logging options specified in the XML configuration file. Default options are: "/L*v". +.PARAMETER LogName + Overrides the default log file name. The default log file name is generated from the MSI file name. If LogName does not end in .log, it will be automatically appended. + For uninstallations, by default the product code is resolved to the DisplayName and version of the application. +.PARAMETER PassThru + Returns ExitCode, STDOut, and STDErr output from the process. +.PARAMETER ContinueOnError + Continue if an error occured while trying to start the processes. Default: $true. +.EXAMPLE + Remove-MSIApplications -Name 'Adobe Flash' + Removes all versions of software that match the name "Adobe Flash" +.EXAMPLE + Remove-MSIApplications -Name 'Adobe' + Removes all versions of software that match the name "Adobe" +.EXAMPLE + Remove-MSIApplications -Name 'Java 8 Update' -FilterApplication ('Is64BitApplication', $false, 'Exact'),('Publisher', 'Oracle Corporation', 'Exact') + Removes all versions of software that match the name "Java 8 Update" where the software is 32-bits and the publisher is "Oracle Corporation". +.EXAMPLE + Remove-MSIApplications -Name 'Java 8 Update' -FilterApplication (,('Publisher', 'Oracle Corporation', 'Exact')) -ExcludeFromUninstall (,('DisplayName', 'Java 8 Update 45', 'Contains')) + Removes all versions of software that match the name "Java 8 Update" and also have "Oracle Corporation" as the Publisher; however, it does not uninstall "Java 8 Update 45" of the software. + NOTE: if only specifying a single row in the two-dimensional arrays, the array must have the extra parentheses and leading comma as in this example. +.EXAMPLE + Remove-MSIApplications -Name 'Java 8 Update' -ExcludeFromUninstall (,('DisplayName', 'Java 8 Update 45', 'Contains')) + Removes all versions of software that match the name "Java 8 Update"; however, it does not uninstall "Java 8 Update 45" of the software. + NOTE: if only specifying a single row in the two-dimensional array, the array must have the extra parentheses and leading comma as in this example. +.EXAMPLE + Remove-MSIApplications -Name 'Java 8 Update' -ExcludeFromUninstall + ('Is64BitApplication', $true, 'Exact'), + ('DisplayName', 'Java 8 Update 45', 'Exact'), + ('DisplayName', 'Java 8 Update 4*', 'WildCard'), + ('DisplayName', 'Java \d Update \d{3}', 'RegEx'), + ('DisplayName', 'Java 8 Update', 'Contains') + Removes all versions of software that match the name "Java 8 Update"; however, it does not uninstall 64-bit versions of the software, Update 45 of the software, or any Update that starts with 4. +.NOTES + More reading on how to create arrays if having trouble with -FilterApplication or -ExcludeFromUninstall parameter: http://blogs.msdn.com/b/powershell/archive/2007/01/23/array-literals-in-powershell.aspx +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [switch]$Exact = $false, + [Parameter(Mandatory=$false)] + [switch]$WildCard = $false, + [Parameter(Mandatory=$false)] + [Alias('Arguments')] + [ValidateNotNullorEmpty()] + [string]$Parameters, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$AddParameters, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [array]$FilterApplication = @(@()), + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [array]$ExcludeFromUninstall = @(@()), + [Parameter(Mandatory=$false)] + [switch]$IncludeUpdatesAndHotfixes = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$LoggingOptions, + [Parameter(Mandatory=$false)] + [Alias('LogName')] + [string]$private:LogName, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$PassThru = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Build the hashtable with the options that will be passed to Get-InstalledApplication using splatting + [hashtable]$GetInstalledApplicationSplat = @{ Name = $name } + If ($Exact) { $GetInstalledApplicationSplat.Add( 'Exact', $Exact) } + ElseIf ($WildCard) { $GetInstalledApplicationSplat.Add( 'WildCard', $WildCard) } + If ($IncludeUpdatesAndHotfixes) { $GetInstalledApplicationSplat.Add( 'IncludeUpdatesAndHotfixes', $IncludeUpdatesAndHotfixes) } + + [psobject[]]$installedApplications = Get-InstalledApplication @GetInstalledApplicationSplat + + Write-Log -Message "Found [$($installedApplications.Count)] application(s) that matched the specified criteria [$Name]." -Source ${CmdletName} + + ## Filter the results from Get-InstalledApplication + [Collections.ArrayList]$removeMSIApplications = New-Object -TypeName 'System.Collections.ArrayList' + If (($null -ne $installedApplications) -and ($installedApplications.Count)) { + ForEach ($installedApplication in $installedApplications) { + If ([string]::IsNullOrEmpty($installedApplication.ProductCode)) { + Write-Log -Message "Skipping removal of application [$($installedApplication.DisplayName)] because unable to discover MSI ProductCode from application's registry Uninstall subkey [$($installedApplication.UninstallSubkey)]." -Severity 2 -Source ${CmdletName} + Continue + } + + # Filter the results from Get-InstalledApplication to only those that should be uninstalled + If (($null -ne $FilterApplication) -and ($FilterApplication.Count)) { + Write-Log -Message "Filter the results to only those that should be uninstalled as specified in parameter [-FilterApplication]." -Source ${CmdletName} + [boolean]$addAppToRemoveList = $false + ForEach ($Filter in $FilterApplication) { + If ($Filter[2] -eq 'RegEx') { + If ($installedApplication.($Filter[0]) -match $Filter[1]) { + [boolean]$addAppToRemoveList = $true + Write-Log -Message "Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of regex match against [-FilterApplication] criteria." -Source ${CmdletName} + } + } + ElseIf ($Filter[2] -eq 'Contains') { + If ($installedApplication.($Filter[0]) -match [regex]::Escape($Filter[1])) { + [boolean]$addAppToRemoveList = $true + Write-Log -Message "Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of contains match against [-FilterApplication] criteria." -Source ${CmdletName} + } + } + ElseIf ($Filter[2] -eq 'WildCard') { + If ($installedApplication.($Filter[0]) -like $Filter[1]) { + [boolean]$addAppToRemoveList = $true + Write-Log -Message "Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of wildcard match against [-FilterApplication] criteria." -Source ${CmdletName} + } + } + ElseIf ($Filter[2] -eq 'Exact') { + If ($installedApplication.($Filter[0]) -eq $Filter[1]) { + [boolean]$addAppToRemoveList = $true + Write-Log -Message "Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of exact match against [-FilterApplication] criteria." -Source ${CmdletName} + } + } + } + } + Else { + [boolean]$addAppToRemoveList = $true + } + + # Filter the results from Get-InstalledApplication to remove those that should never be uninstalled + If (($null -ne $ExcludeFromUninstall) -and ($ExcludeFromUninstall.Count)) { + ForEach ($Exclude in $ExcludeFromUninstall) { + If ($Exclude[2] -eq 'RegEx') { + If ($installedApplication.($Exclude[0]) -match $Exclude[1]) { + [boolean]$addAppToRemoveList = $false + Write-Log -Message "Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of regex match against [-ExcludeFromUninstall] criteria." -Source ${CmdletName} + } + } + ElseIf ($Exclude[2] -eq 'Contains') { + If ($installedApplication.($Exclude[0]) -match [regex]::Escape($Exclude[1])) { + [boolean]$addAppToRemoveList = $false + Write-Log -Message "Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of contains match against [-ExcludeFromUninstall] criteria." -Source ${CmdletName} + } + } + ElseIf ($Exclude[2] -eq 'WildCard') { + If ($installedApplication.($Exclude[0]) -like $Exclude[1]) { + [boolean]$addAppToRemoveList = $false + Write-Log -Message "Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of wildcard match against [-ExcludeFromUninstall] criteria." -Source ${CmdletName} + } + } + ElseIf ($Exclude[2] -eq 'Exact') { + If ($installedApplication.($Exclude[0]) -eq $Exclude[1]) { + [boolean]$addAppToRemoveList = $false + Write-Log -Message "Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of exact match against [-ExcludeFromUninstall] criteria." -Source ${CmdletName} + } + } + } + } + + If ($addAppToRemoveList) { + Write-Log -Message "Adding application to list for removal: [$($installedApplication.DisplayName) $($installedApplication.Version)]." -Source ${CmdletName} + $removeMSIApplications.Add($installedApplication) + } + } + } + + ## Build the hashtable with the options that will be passed to Execute-MSI using splatting + [hashtable]$ExecuteMSISplat = @{ Action = 'Uninstall'; Path = '' } + If ($ContinueOnError) { $ExecuteMSISplat.Add( 'ContinueOnError', $ContinueOnError) } + If ($Parameters) { $ExecuteMSISplat.Add( 'Parameters', $Parameters) } + ElseIf ($AddParameters) { $ExecuteMSISplat.Add( 'AddParameters', $AddParameters) } + If ($LoggingOptions) { $ExecuteMSISplat.Add( 'LoggingOptions', $LoggingOptions) } + If ($LogName) { $ExecuteMSISplat.Add( 'LogName', $LogName) } + If ($PassThru) { $ExecuteMSISplat.Add( 'PassThru', $PassThru) } + If ($IncludeUpdatesAndHotfixes) { $ExecuteMSISplat.Add( 'IncludeUpdatesAndHotfixes', $IncludeUpdatesAndHotfixes) } + + If (($null -ne $removeMSIApplications) -and ($removeMSIApplications.Count)) { + ForEach ($removeMSIApplication in $removeMSIApplications) { + Write-Log -Message "Remove application [$($removeMSIApplication.DisplayName) $($removeMSIApplication.Version)]." -Source ${CmdletName} + $ExecuteMSISplat.Path = $removeMSIApplication.ProductCode + If ($PassThru) { + [psobject[]]$ExecuteResults += Execute-MSI @ExecuteMSISplat + } + Else { + Execute-MSI @ExecuteMSISplat + } + } + } + Else { + Write-Log -Message 'No applications found for removal. Continue...' -Source ${CmdletName} + } + } + End { + If ($PassThru) { Write-Output -InputObject $ExecuteResults } + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Execute-Process +Function Execute-Process { +<# +.SYNOPSIS + Execute a process with optional arguments, working directory, window style. +.DESCRIPTION + Executes a process, e.g. a file included in the Files directory of the App Deploy Toolkit, or a file on the local machine. + Provides various options for handling the return codes (see Parameters). +.PARAMETER Path + Path to the file to be executed. If the file is located directly in the "Files" directory of the App Deploy Toolkit, only the file name needs to be specified. + Otherwise, the full path of the file must be specified. If the files is in a subdirectory of "Files", use the "$dirFiles" variable as shown in the example. +.PARAMETER Parameters + Arguments to be passed to the executable +.PARAMETER SecureParameters + Hides all parameters passed to the executable from the Toolkit log file +.PARAMETER WindowStyle + Style of the window of the process executed. Options: Normal, Hidden, Maximized, Minimized. Default: Normal. + Note: Not all processes honor WindowStyle. WindowStyle is a recommendation passed to the process. They can choose to ignore it. + Only works for native Windows GUI applications. If the WindowStyle is set to Hidden, UseShellExecute should be set to $true. +.PARAMETER CreateNoWindow + Specifies whether the process should be started with a new window to contain it. Only works for Console mode applications. UseShellExecute should be set to $false. + Default is false. +.PARAMETER WorkingDirectory + The working directory used for executing the process. Defaults to the directory of the file being executed. + Parameter UseShellExecute affects this parameter. +.PARAMETER NoWait + Immediately continue after executing the process. +.PARAMETER PassThru + If NoWait is not specified, returns an object with ExitCode, STDOut and STDErr output from the process. If NoWait is specified, returns an object with Id, Handle and ProcessName. +.PARAMETER WaitForMsiExec + Sometimes an EXE bootstrapper will launch an MSI install. In such cases, this variable will ensure that + this function waits for the msiexec engine to become available before starting the install. +.PARAMETER MsiExecWaitTime + Specify the length of time in seconds to wait for the msiexec engine to become available. Default: 600 seconds (10 minutes). +.PARAMETER IgnoreExitCodes + List the exit codes to ignore or * to ignore all exit codes. +.PARAMETER PriorityClass + Specifies priority class for the process. Options: Idle, Normal, High, AboveNormal, BelowNormal, RealTime. Default: Normal +.PARAMETER ExitOnProcessFailure + Specifies whether the function should call Exit-Script when the process returns an exit code that is considered an error/failure. Default: $true +.PARAMETER UseShellExecute + Specifies whether to use the operating system shell to start the process. $true if the shell should be used when starting the process; $false if the process should be created directly from the executable file. + The word "Shell" in this context refers to a graphical shell (similar to the Windows shell) rather than command shells (for example, bash or sh) and lets users launch graphical applications or open documents. + It lets you open a file or a url and the Shell will figure out the program to open it with. + The WorkingDirectory property behaves differently depending on the value of the UseShellExecute property. When UseShellExecute is true, the WorkingDirectory property specifies the location of the executable. + When UseShellExecute is false, the WorkingDirectory property is not used to find the executable. Instead, it is used only by the process that is started and has meaning only within the context of the new process. + If you set UseShellExecute to $true, there will be no available output from the process. + Default: $false +.PARAMETER ContinueOnError + Continue if an error occured while trying to start the process. Default: $false. +.EXAMPLE + Execute-Process -Path 'uninstall_flash_player_64bit.exe' -Parameters '/uninstall' -WindowStyle 'Hidden' + If the file is in the "Files" directory of the App Deploy Toolkit, only the file name needs to be specified. +.EXAMPLE + Execute-Process -Path "$dirFiles\Bin\setup.exe" -Parameters '/S' -WindowStyle 'Hidden' +.EXAMPLE + Execute-Process -Path 'setup.exe' -Parameters '/S' -IgnoreExitCodes '1,2' +.EXAMPLE + Execute-Process -Path 'setup.exe' -Parameters "-s -f2`"$configToolkitLogDir\$installName.log`"" + Launch InstallShield "setup.exe" from the ".\Files" sub-directory and force log files to the logging folder. +.EXAMPLE + Execute-Process -Path 'setup.exe' -Parameters "/s /v`"ALLUSERS=1 /qn /L* \`"$configToolkitLogDir\$installName.log`"`"" + Launch InstallShield "setup.exe" with embedded MSI and force log files to the logging folder. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [Alias('FilePath')] + [ValidateNotNullorEmpty()] + [string]$Path, + [Parameter(Mandatory=$false)] + [Alias('Arguments')] + [ValidateNotNullorEmpty()] + [string[]]$Parameters, + [Parameter(Mandatory=$false)] + [switch]$SecureParameters = $false, + [Parameter(Mandatory=$false)] + [ValidateSet('Normal','Hidden','Maximized','Minimized')] + [Diagnostics.ProcessWindowStyle]$WindowStyle = 'Normal', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$CreateNoWindow = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$WorkingDirectory, + [Parameter(Mandatory=$false)] + [switch]$NoWait = $false, + [Parameter(Mandatory=$false)] + [switch]$PassThru = $false, + [Parameter(Mandatory=$false)] + [switch]$WaitForMsiExec = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int]$MsiExecWaitTime = $configMSIMutexWaitTime, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$IgnoreExitCodes, + [Parameter(Mandatory=$false)] + [ValidateSet('Idle', 'Normal', 'High', 'AboveNormal', 'BelowNormal', 'RealTime')] + [Diagnostics.ProcessPriorityClass]$PriorityClass = 'Normal', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ExitOnProcessFailure = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$UseShellExecute = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + $private:returnCode = $null + + ## Validate and find the fully qualified path for the $Path variable. + If (([IO.Path]::IsPathRooted($Path)) -and ([IO.Path]::HasExtension($Path))) { + Write-Log -Message "[$Path] is a valid fully qualified path, continue." -Source ${CmdletName} + If (-not (Test-Path -LiteralPath $Path -PathType 'Leaf' -ErrorAction 'Stop')) { + Write-Log -Message "File [$Path] not found." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "File [$Path] not found." + } + Return + } + } + Else { + # The first directory to search will be the 'Files' subdirectory of the script directory + [string]$PathFolders = $dirFiles + # Add the current location of the console (Windows always searches this location first) + [string]$PathFolders = $PathFolders + ';' + (Get-Location -PSProvider 'FileSystem').Path + # Add the new path locations to the PATH environment variable + $env:PATH = $PathFolders + ';' + $env:PATH + + # Get the fully qualified path for the file. Get-Command searches PATH environment variable to find this value. + [string]$FullyQualifiedPath = Get-Command -Name $Path -CommandType 'Application' -TotalCount 1 -Syntax -ErrorAction 'Stop' + + # Revert the PATH environment variable to it's original value + $env:PATH = $env:PATH -replace [regex]::Escape($PathFolders + ';'), '' + + If ($FullyQualifiedPath) { + Write-Log -Message "[$Path] successfully resolved to fully qualified path [$FullyQualifiedPath]." -Source ${CmdletName} + $Path = $FullyQualifiedPath + } + Else { + Write-Log -Message "[$Path] contains an invalid path or file name." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "[$Path] contains an invalid path or file name." + } + Return + } + } + + ## Set the Working directory (if not specified) + If (-not $WorkingDirectory) { $WorkingDirectory = Split-Path -Path $Path -Parent -ErrorAction 'Stop' } + + ## If MSI install, check to see if the MSI installer service is available or if another MSI install is already underway. + ## Please note that a race condition is possible after this check where another process waiting for the MSI installer + ## to become available grabs the MSI Installer mutex before we do. Not too concerned about this possible race condition. + If (($Path -match 'msiexec') -or ($WaitForMsiExec)) { + [timespan]$MsiExecWaitTimeSpan = New-TimeSpan -Seconds $MsiExecWaitTime + [boolean]$MsiExecAvailable = Test-IsMutexAvailable -MutexName 'Global\_MSIExecute' -MutexWaitTimeInMilliseconds $MsiExecWaitTimeSpan.TotalMilliseconds + Start-Sleep -Seconds 1 + If (-not $MsiExecAvailable) { + # Default MSI exit code for install already in progress + [int32]$returnCode = 1618 + Write-Log -Message "Another MSI installation is already in progress and needs to be completed before proceeding with this installation." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw 'Another MSI installation is already in progress and needs to be completed before proceeding with this installation.' + } + Return + } + } + + Try { + ## Disable Zone checking to prevent warnings when running executables + $env:SEE_MASK_NOZONECHECKS = 1 + + ## Using this variable allows capture of exceptions from .NET methods. Private scope only changes value for current function. + $private:previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + ## Define process + $processStartInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' -ErrorAction 'Stop' + $processStartInfo.FileName = $Path + $processStartInfo.WorkingDirectory = $WorkingDirectory + $processStartInfo.UseShellExecute = $UseShellExecute + $processStartInfo.ErrorDialog = $false + $processStartInfo.RedirectStandardOutput = $true + $processStartInfo.RedirectStandardError = $true + $processStartInfo.CreateNoWindow = $CreateNoWindow + If ($Parameters) { $processStartInfo.Arguments = $Parameters } + $processStartInfo.WindowStyle = $WindowStyle + If ($processStartInfo.UseShellExecute -eq $true) { + Write-Log -Message "UseShellExecute is set to true, standard output and error will not be available." -Source ${CmdletName} + $processStartInfo.RedirectStandardOutput = $false + $processStartInfo.RedirectStandardError = $false + } + $process = New-Object -TypeName 'System.Diagnostics.Process' -ErrorAction 'Stop' + $process.StartInfo = $processStartInfo + + If ($processStartInfo.UseShellExecute -eq $false) { + ## Add event handler to capture process's standard output redirection + [scriptblock]$processEventHandler = { If (-not [string]::IsNullOrEmpty($EventArgs.Data)) { $Event.MessageData.AppendLine($EventArgs.Data) } } + $stdOutBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList '' + $stdOutEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'OutputDataReceived' -MessageData $stdOutBuilder -ErrorAction 'Stop' + $stdErrBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList '' + $stdErrEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'ErrorDataReceived' -MessageData $stdErrBuilder -ErrorAction 'Stop' + } + + ## Start Process + Write-Log -Message "Working Directory is [$WorkingDirectory]." -Source ${CmdletName} + If ($Parameters) { + If ($Parameters -match '-Command \&') { + Write-Log -Message "Executing [$Path [PowerShell ScriptBlock]]..." -Source ${CmdletName} + } + Else { + If ($SecureParameters) { + Write-Log -Message "Executing [$Path (Parameters Hidden)]..." -Source ${CmdletName} + } + Else { + Write-Log -Message "Executing [$Path $Parameters]..." -Source ${CmdletName} + } + } + } + Else { + Write-Log -Message "Executing [$Path]..." -Source ${CmdletName} + } + + $null = $process.Start() + ## Set priority + If ($PriorityClass -ne "Normal") { + try { + If ($process.HasExited -eq $False) { + Write-Log -Message "Changing the priority class for the process to [$PriorityClass]" -Source ${CmdletName} + $process.PriorityClass = $PriorityClass + } + Else { + Write-Log -Message "Cannot change the priority class for the process to [$PriorityClass], because the process has exited already." -Severity 2 -Source ${CmdletName} + } + + } + catch { + Write-Log -Message "Failed to change the priority class for the process." -Severity 2 -Source ${CmdletName} + } + } + ## NoWait specified, return process details. If it isnt specified, start reading standard Output and Error streams + If ($NoWait) { + Write-Log -Message 'NoWait parameter specified. Continuing without waiting for exit code...' -Source ${CmdletName} + + If ($PassThru) { + If ($process.HasExited -eq $false) { + Write-Log -Message "PassThru parameter specified, returning process details object." -Source ${CmdletName} + [psobject]$ProcessDetails = New-Object -TypeName 'PSObject' -Property @{ Id = If ($process.Id) {$process.Id} Else { $null } ; Handle = If ($process.Handle) { $process.Handle } Else { [IntPtr]::Zero }; ProcessName = If ($process.ProcessName) { $process.ProcessName } Else { '' } } + Write-Output -InputObject $ProcessDetails + } + Else { + Write-Log -Message "PassThru parameter specified, however the process has already exited." -Source ${CmdletName} + } + } + } + Else { + If ($processStartInfo.UseShellExecute -eq $false) { + $process.BeginOutputReadLine() + $process.BeginErrorReadLine() + } + ## Instructs the Process component to wait indefinitely for the associated process to exit. + $process.WaitForExit() + + ## HasExited indicates that the associated process has terminated, either normally or abnormally. Wait until HasExited returns $true. + While (-not ($process.HasExited)) { $process.Refresh(); Start-Sleep -Seconds 1 } + + ## Get the exit code for the process + Try { + [int32]$returnCode = $process.ExitCode + } + Catch [System.Management.Automation.PSInvalidCastException] { + # Catch exit codes that are out of int32 range + [int32]$returnCode = 60013 + } + + If ($processStartInfo.UseShellExecute -eq $false) { + ## Unregister standard output and error event to retrieve process output + If ($stdOutEvent) { Unregister-Event -SourceIdentifier $stdOutEvent.Name -ErrorAction 'Stop'; $stdOutEvent = $null } + If ($stdErrEvent) { Unregister-Event -SourceIdentifier $stdErrEvent.Name -ErrorAction 'Stop'; $stdErrEvent = $null } + $stdOut = $stdOutBuilder.ToString() -replace $null,'' + $stdErr = $stdErrBuilder.ToString() -replace $null,'' + + If ($stdErr.Length -gt 0) { + Write-Log -Message "Standard error output from the process: $stdErr" -Severity 3 -Source ${CmdletName} + } + } + } + } + Finally { + If ($processStartInfo.UseShellExecute -eq $false) { + ## Make sure the standard output and error event is unregistered + If ($stdOutEvent) { Unregister-Event -SourceIdentifier $stdOutEvent.Name -ErrorAction 'Stop'; $stdOutEvent = $null } + If ($stdErrEvent) { Unregister-Event -SourceIdentifier $stdErrEvent.Name -ErrorAction 'Stop'; $stdErrEvent = $null } + } + ## Free resources associated with the process, this does not cause process to exit + If ($process) { $process.Dispose() } + + ## Re-enable Zone checking + Remove-Item -LiteralPath 'env:SEE_MASK_NOZONECHECKS' -ErrorAction 'SilentlyContinue' + + If ($private:previousErrorActionPreference) { $ErrorActionPreference = $private:previousErrorActionPreference } + } + + If (-not $NoWait) { + ## Check to see whether we should ignore exit codes + $ignoreExitCodeMatch = $false + If ($ignoreExitCodes) { + ## Check whether * was specified, which would tell us to ignore all exit codes + If ($ignoreExitCodes.Trim() -eq "*") { + $ignoreExitCodeMatch = $true + } + Else { + ## Split the processes on a comma + [int32[]]$ignoreExitCodesArray = $ignoreExitCodes -split ',' + ForEach ($ignoreCode in $ignoreExitCodesArray) { + If ($returnCode -eq $ignoreCode) { $ignoreExitCodeMatch = $true } + } + } + } + + ## If the passthru switch is specified, return the exit code and any output from process + If ($PassThru) { + Write-Log -Message "PassThru parameter specified, returning execution results object." -Source ${CmdletName} + [psobject]$ExecutionResults = New-Object -TypeName 'PSObject' -Property @{ ExitCode = $returnCode; StdOut = If ($stdOut) { $stdOut } Else { '' }; StdErr = If ($stdErr) { $stdErr } Else { '' } } + Write-Output -InputObject $ExecutionResults + } + + If ($ignoreExitCodeMatch) { + Write-Log -Message "Execution completed and the exit code [$returncode] is being ignored." -Source ${CmdletName} + } + ElseIf (($returnCode -eq 3010) -or ($returnCode -eq 1641)) { + Write-Log -Message "Execution completed successfully with exit code [$returnCode]. A reboot is required." -Severity 2 -Source ${CmdletName} + Set-Variable -Name 'msiRebootDetected' -Value $true -Scope 'Script' + } + ElseIf (($returnCode -eq 1605) -and ($Path -match 'msiexec')) { + Write-Log -Message "Execution failed with exit code [$returnCode] because the product is not currently installed." -Severity 3 -Source ${CmdletName} + } + ElseIf (($returnCode -eq -2145124329) -and ($Path -match 'wusa')) { + Write-Log -Message "Execution failed with exit code [$returnCode] because the Windows Update is not applicable to this system." -Severity 3 -Source ${CmdletName} + } + ElseIf (($returnCode -eq 17025) -and ($Path -match 'fullfile')) { + Write-Log -Message "Execution failed with exit code [$returnCode] because the Office Update is not applicable to this system." -Severity 3 -Source ${CmdletName} + } + ElseIf ($returnCode -eq 0) { + Write-Log -Message "Execution completed successfully with exit code [$returnCode]." -Source ${CmdletName} + } + Else { + [string]$MsiExitCodeMessage = '' + If ($Path -match 'msiexec') { + [string]$MsiExitCodeMessage = Get-MsiExitCodeMessage -MsiExitCode $returnCode + } + + If ($MsiExitCodeMessage) { + Write-Log -Message "Execution failed with exit code [$returnCode]: $MsiExitCodeMessage" -Severity 3 -Source ${CmdletName} + } + Else { + Write-Log -Message "Execution failed with exit code [$returnCode]." -Severity 3 -Source ${CmdletName} + } + + If ($ExitOnProcessFailure) { + Exit-Script -ExitCode $returnCode + } + } + } + } + Catch { + If ([string]::IsNullOrEmpty([string]$returnCode)) { + [int32]$returnCode = 60002 + Write-Log -Message "Function failed, setting exit code to [$returnCode]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Function failed, setting exit code to [$returnCode]. `n$(Resolve-Error)" + } + } + Else { + Write-Log -Message "Execution completed with exit code [$returnCode]. Function failed. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + If ($PassThru) { + [psobject]$ExecutionResults = New-Object -TypeName 'PSObject' -Property @{ ExitCode = $returnCode; StdOut = If ($stdOut) { $stdOut } Else { '' }; StdErr = If ($stdErr) { $stdErr } Else { '' } } + Write-Output -InputObject $ExecutionResults + } + + If ($ExitOnProcessFailure) { + Exit-Script -ExitCode $returnCode + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-MsiExitCodeMessage +Function Get-MsiExitCodeMessage { +<# +.SYNOPSIS + Get message for MSI error code +.DESCRIPTION + Get message for MSI error code by reading it from msimsg.dll +.PARAMETER MsiErrorCode + MSI error code +.EXAMPLE + Get-MsiExitCodeMessage -MsiErrorCode 1618 +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://msdn.microsoft.com/en-us/library/aa368542(v=vs.85).aspx + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [int32]$MsiExitCode + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Get message for exit code [$MsiExitCode]." -Source ${CmdletName} + [string]$MsiExitCodeMsg = [PSADT.Msi]::GetMessageFromMsiExitCode($MsiExitCode) + Write-Output -InputObject $MsiExitCodeMsg + } + Catch { + Write-Log -Message "Failed to get message for exit code [$MsiExitCode]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-IsMutexAvailable +Function Test-IsMutexAvailable { +<# +.SYNOPSIS + Wait, up to a timeout value, to check if current thread is able to acquire an exclusive lock on a system mutex. +.DESCRIPTION + A mutex can be used to serialize applications and prevent multiple instances from being opened at the same time. + Wait, up to a timeout (default is 1 millisecond), for the mutex to become available for an exclusive lock. +.PARAMETER MutexName + The name of the system mutex. +.PARAMETER MutexWaitTime + The number of milliseconds the current thread should wait to acquire an exclusive lock of a named mutex. Default is: 1 millisecond. + A wait time of -1 milliseconds means to wait indefinitely. A wait time of zero does not acquire an exclusive lock but instead tests the state of the wait handle and returns immediately. +.EXAMPLE + Test-IsMutexAvailable -MutexName 'Global\_MSIExecute' -MutexWaitTimeInMilliseconds 500 +.EXAMPLE + Test-IsMutexAvailable -MutexName 'Global\_MSIExecute' -MutexWaitTimeInMilliseconds (New-TimeSpan -Minutes 5).TotalMilliseconds +.EXAMPLE + Test-IsMutexAvailable -MutexName 'Global\_MSIExecute' -MutexWaitTimeInMilliseconds (New-TimeSpan -Seconds 60).TotalMilliseconds +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://msdn.microsoft.com/en-us/library/aa372909(VS.85).asp + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateLength(1,260)] + [string]$MutexName, + [Parameter(Mandatory=$false)] + [ValidateScript({($_ -ge -1) -and ($_ -le [int32]::MaxValue)})] + [int32]$MutexWaitTimeInMilliseconds = 1 + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Initialize Variables + [timespan]$MutexWaitTime = [timespan]::FromMilliseconds($MutexWaitTimeInMilliseconds) + If ($MutexWaitTime.TotalMinutes -ge 1) { + [string]$WaitLogMsg = "$($MutexWaitTime.TotalMinutes) minute(s)" + } + ElseIf ($MutexWaitTime.TotalSeconds -ge 1) { + [string]$WaitLogMsg = "$($MutexWaitTime.TotalSeconds) second(s)" + } + Else { + [string]$WaitLogMsg = "$($MutexWaitTime.Milliseconds) millisecond(s)" + } + [boolean]$IsUnhandledException = $false + [boolean]$IsMutexFree = $false + [Threading.Mutex]$OpenExistingMutex = $null + } + Process { + Write-Log -Message "Check to see if mutex [$MutexName] is available. Wait up to [$WaitLogMsg] for the mutex to become available." -Source ${CmdletName} + Try { + ## Using this variable allows capture of exceptions from .NET methods. Private scope only changes value for current function. + $private:previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + ## Open the specified named mutex, if it already exists, without acquiring an exclusive lock on it. If the system mutex does not exist, this method throws an exception instead of creating the system object. + [Threading.Mutex]$OpenExistingMutex = [Threading.Mutex]::OpenExisting($MutexName) + ## Attempt to acquire an exclusive lock on the mutex. Use a Timespan to specify a timeout value after which no further attempt is made to acquire a lock on the mutex. + $IsMutexFree = $OpenExistingMutex.WaitOne($MutexWaitTime, $false) + } + Catch [Threading.WaitHandleCannotBeOpenedException] { + ## The named mutex does not exist + $IsMutexFree = $true + } + Catch [ObjectDisposedException] { + ## Mutex was disposed between opening it and attempting to wait on it + $IsMutexFree = $true + } + Catch [UnauthorizedAccessException] { + ## The named mutex exists, but the user does not have the security access required to use it + $IsMutexFree = $false + } + Catch [Threading.AbandonedMutexException] { + ## The wait completed because a thread exited without releasing a mutex. This exception is thrown when one thread acquires a mutex object that another thread has abandoned by exiting without releasing it. + $IsMutexFree = $true + } + Catch { + $IsUnhandledException = $true + ## Return $true, to signify that mutex is available, because function was unable to successfully complete a check due to an unhandled exception. Default is to err on the side of the mutex being available on a hard failure. + Write-Log -Message "Unable to check if mutex [$MutexName] is available due to an unhandled exception. Will default to return value of [$true]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + $IsMutexFree = $true + } + Finally { + If ($IsMutexFree) { + If (-not $IsUnhandledException) { + Write-Log -Message "Mutex [$MutexName] is available for an exclusive lock." -Source ${CmdletName} + } + } + Else { + If ($MutexName -eq 'Global\_MSIExecute') { + ## Get the command line for the MSI installation in progress + Try { + [string]$msiInProgressCmdLine = Get-WmiObject -Class 'Win32_Process' -Filter "name = 'msiexec.exe'" -ErrorAction 'Stop' | Where-Object { $_.CommandLine } | Select-Object -ExpandProperty 'CommandLine' | Where-Object { $_ -match '\.msi' } | ForEach-Object { $_.Trim() } + } + Catch { } + Write-Log -Message "Mutex [$MutexName] is not available for an exclusive lock because the following MSI installation is in progress [$msiInProgressCmdLine]." -Severity 2 -Source ${CmdletName} + } + Else { + Write-Log -Message "Mutex [$MutexName] is not available because another thread already has an exclusive lock on it." -Source ${CmdletName} + } + } + + If (($null -ne $OpenExistingMutex) -and ($IsMutexFree)) { + ## Release exclusive lock on the mutex + $null = $OpenExistingMutex.ReleaseMutex() + $OpenExistingMutex.Close() + } + If ($private:previousErrorActionPreference) { $ErrorActionPreference = $private:previousErrorActionPreference } + } + } + End { + Write-Output -InputObject $IsMutexFree + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function New-Folder +Function New-Folder { +<# +.SYNOPSIS + Create a new folder. +.DESCRIPTION + Create a new folder if it does not exist. +.PARAMETER Path + Path to the new folder to create. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + New-Folder -Path "$envWinDir\System32" +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Path, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + If (-not (Test-Path -LiteralPath $Path -PathType 'Container')) { + Write-Log -Message "Create folder [$Path]." -Source ${CmdletName} + $null = New-Item -Path $Path -ItemType 'Directory' -ErrorAction 'Stop' + } + Else { + Write-Log -Message "Folder [$Path] already exists." -Source ${CmdletName} + } + } + Catch { + Write-Log -Message "Failed to create folder [$Path]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to create folder [$Path]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Remove-Folder +Function Remove-Folder { +<# +.SYNOPSIS + Remove folder and files if they exist. +.DESCRIPTION + Remove folder and all files with or without recursion in a given path. +.PARAMETER Path + Path to the folder to remove. +.PARAMETER DisableRecursion + Disables recursion while deleting. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Remove-Folder -Path "$envWinDir\Downloaded Program Files" +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Path, + [Parameter(Mandatory=$false)] + [switch]$DisableRecursion, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If (Test-Path -LiteralPath $Path -PathType 'Container') { + Try { + If ($DisableRecursion) { + Write-Log -Message "Delete folder [$path] without recursion..." -Source ${CmdletName} + Remove-Item -LiteralPath $Path -Force -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorRemoveFolder' + } else { + Write-Log -Message "Delete folder [$path] recursively..." -Source ${CmdletName} + Remove-Item -LiteralPath $Path -Force -Recurse -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorRemoveFolder' + } + + If ($ErrorRemoveFolder) { + Write-Log -Message "The following error(s) took place while deleting folder(s) and file(s) recursively from path [$path]. `n$(Resolve-Error -ErrorRecord $ErrorRemoveFolder)" -Severity 2 -Source ${CmdletName} + } + } + Catch { + Write-Log -Message "Failed to delete folder(s) and file(s) recursively from path [$path]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to delete folder(s) and file(s) recursively from path [$path]: $($_.Exception.Message)" + } + } + } + Else { + Write-Log -Message "Folder [$Path] does not exists..." -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Copy-File +Function Copy-File { +<# +.SYNOPSIS + Copy a file or group of files to a destination path. +.DESCRIPTION + Copy a file or group of files to a destination path. +.PARAMETER Path + Path of the file to copy. +.PARAMETER Destination + Destination Path of the file to copy. +.PARAMETER Recurse + Copy files in subdirectories. +.PARAMETER Flatten + Flattens the files into the root destination directory. +.PARAMETER ContinueOnError + Continue if an error is encountered. This will continue the deployment script, but will not continue copying files if an error is encountered. Default is: $true. +.PARAMETER ContinueFileCopyOnError + Continue copying files if an error is encountered. This will continue the deployment script and will warn about files that failed to be copied. Default is: $false. +.EXAMPLE + Copy-File -Path "$dirSupportFiles\MyApp.ini" -Destination "$envWinDir\MyApp.ini" +.EXAMPLE + Copy-File -Path "$dirSupportFiles\*.*" -Destination "$envTemp\tempfiles" + Copy all of the files in a folder to a destination folder. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string[]]$Path, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Destination, + [Parameter(Mandatory=$false)] + [switch]$Recurse = $false, + [Parameter(Mandatory=$false)] + [switch]$Flatten, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true, + [ValidateNotNullOrEmpty()] + [boolean]$ContinueFileCopyOnError = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + $null = $fileCopyError + If ((-not ([IO.Path]::HasExtension($Destination))) -and (-not (Test-Path -LiteralPath $Destination -PathType 'Container'))) { + Write-Log -Message "Destination folder does not exist, creating destination folder [$destination]." -Source ${CmdletName} + $null = New-Item -Path $Destination -Type 'Directory' -Force -ErrorAction 'Stop' + } + + if ($Flatten) { + If ($Recurse) { + Write-Log -Message "Copy file(s) recursively in path [$path] to destination [$destination] root folder, flattened." -Source ${CmdletName} + If (-not $ContinueFileCopyOnError) { + $null = Get-ChildItem -Path $path -Recurse | Where-Object {!($_.PSIsContainer)} | ForEach-Object { + Copy-Item -Path ($_.FullName) -Destination $destination -Force -ErrorAction 'Stop' + } + } + Else { + $null = Get-ChildItem -Path $path -Recurse | Where-Object {!($_.PSIsContainer)} | ForEach-Object { + Copy-Item -Path ($_.FullName) -Destination $destination -Force -ErrorAction 'SilentlyContinue' -ErrorVariable FileCopyError + } + } + } + Else { + Write-Log -Message "Copy file in path [$path] to destination [$destination]." -Source ${CmdletName} + If (-not $ContinueFileCopyOnError) { + $null = Copy-Item -Path $path -Destination $destination -Force -ErrorAction 'Stop' + } + Else { + $null = Copy-Item -Path $path -Destination $destination -Force -ErrorAction 'SilentlyContinue' -ErrorVariable FileCopyError + } + } + } + Else { + $null = $FileCopyError + If ($Recurse) { + Write-Log -Message "Copy file(s) recursively in path [$path] to destination [$destination]." -Source ${CmdletName} + If (-not $ContinueFileCopyOnError) { + $null = Copy-Item -Path $Path -Destination $Destination -Force -Recurse -ErrorAction 'Stop' + } + Else { + $null = Copy-Item -Path $Path -Destination $Destination -Force -Recurse -ErrorAction 'SilentlyContinue' -ErrorVariable FileCopyError + } + } + Else { + Write-Log -Message "Copy file in path [$path] to destination [$destination]." -Source ${CmdletName} + If (-not $ContinueFileCopyOnError) { + $null = Copy-Item -Path $Path -Destination $Destination -Force -ErrorAction 'Stop' + } + Else { + $null = Copy-Item -Path $Path -Destination $Destination -Force -ErrorAction 'SilentlyContinue' -ErrorVariable FileCopyError + } + } + } + + If ($fileCopyError) { + Write-Log -Message "The following warnings were detected while copying file(s) in path [$path] to destination [$destination]. `n$FileCopyError" -Severity 2 -Source ${CmdletName} + } + Else { + Write-Log -Message "File copy completed successfully." -Source ${CmdletName} + } + } + Catch { + Write-Log -Message "Failed to copy file(s) in path [$path] to destination [$destination]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to copy file(s) in path [$path] to destination [$destination]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Remove-File +Function Remove-File { +<# +.SYNOPSIS + Removes one or more items from a given path on the filesystem. +.DESCRIPTION + Removes one or more items from a given path on the filesystem. +.PARAMETER Path + Specifies the path on the filesystem to be resolved. The value of Path will accept wildcards. Will accept an array of values. +.PARAMETER LiteralPath + Specifies the path on the filesystem to be resolved. The value of LiteralPath is used exactly as it is typed; no characters are interpreted as wildcards. Will accept an array of values. +.PARAMETER Recurse + Deletes the files in the specified location(s) and in all child items of the location(s). +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Remove-File -Path 'C:\Windows\Downloaded Program Files\Temp.inf' +.EXAMPLE + Remove-File -LiteralPath 'C:\Windows\Downloaded Program Files' -Recurse +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,ParameterSetName='Path')] + [ValidateNotNullorEmpty()] + [string[]]$Path, + [Parameter(Mandatory=$true,ParameterSetName='LiteralPath')] + [ValidateNotNullorEmpty()] + [string[]]$LiteralPath, + [Parameter(Mandatory=$false)] + [switch]$Recurse = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Build hashtable of parameters/value pairs to be passed to Remove-Item cmdlet + [hashtable]$RemoveFileSplat = @{ 'Recurse' = $Recurse + 'Force' = $true + 'ErrorVariable' = '+ErrorRemoveItem' + } + If ($ContinueOnError) { + $RemoveFileSplat.Add('ErrorAction', 'SilentlyContinue') + } + Else { + $RemoveFileSplat.Add('ErrorAction', 'Stop') + } + + ## Resolve the specified path, if the path does not exist, display a warning instead of an error + If ($PSCmdlet.ParameterSetName -eq 'Path') { [string[]]$SpecifiedPath = $Path } Else { [string[]]$SpecifiedPath = $LiteralPath } + ForEach ($Item in $SpecifiedPath) { + Try { + If ($PSCmdlet.ParameterSetName -eq 'Path') { + [string[]]$ResolvedPath += Resolve-Path -Path $Item -ErrorAction 'Stop' | Where-Object { $_.Path } | Select-Object -ExpandProperty 'Path' -ErrorAction 'Stop' + } + Else { + [string[]]$ResolvedPath += Resolve-Path -LiteralPath $Item -ErrorAction 'Stop' | Where-Object { $_.Path } | Select-Object -ExpandProperty 'Path' -ErrorAction 'Stop' + } + } + Catch [System.Management.Automation.ItemNotFoundException] { + Write-Log -Message "Unable to resolve file(s) for deletion in path [$Item] because path does not exist." -Severity 2 -Source ${CmdletName} + } + Catch { + Write-Log -Message "Failed to resolve file(s) for deletion in path [$Item]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to resolve file(s) for deletion in path [$Item]: $($_.Exception.Message)" + } + } + } + + ## Delete specified path if it was successfully resolved + If ($ResolvedPath) { + ForEach ($Item in $ResolvedPath) { + Try { + If (($Recurse) -and (Test-Path -LiteralPath $Item -PathType 'Container')) { + Write-Log -Message "Delete file(s) recursively in path [$Item]..." -Source ${CmdletName} + } + ElseIf ((-not $Recurse) -and (Test-Path -LiteralPath $Item -PathType 'Container')) { + Write-Log -Message "Skipping folder [$Item] because the Recurse switch was not specified" -Source ${CmdletName} + Continue + } + Else { + Write-Log -Message "Delete file in path [$Item]..." -Source ${CmdletName} + } + $null = Remove-Item @RemoveFileSplat -LiteralPath $Item + } + Catch { + Write-Log -Message "Failed to delete file(s) in path [$Item]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to delete file(s) in path [$Item]: $($_.Exception.Message)" + } + } + } + } + + If ($ErrorRemoveItem) { + Write-Log -Message "The following error(s) took place while removing file(s) in path [$SpecifiedPath]. `n$(Resolve-Error -ErrorRecord $ErrorRemoveItem)" -Severity 2 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Convert-RegistryPath +Function Convert-RegistryPath { +<# +.SYNOPSIS + Converts the specified registry key path to a format that is compatible with built-in PowerShell cmdlets. +.DESCRIPTION + Converts the specified registry key path to a format that is compatible with built-in PowerShell cmdlets. + Converts registry key hives to their full paths. Example: HKLM is converted to "Registry::HKEY_LOCAL_MACHINE". +.PARAMETER Key + Path to the registry key to convert (can be a registry hive or fully qualified path) +.PARAMETER SID + The security identifier (SID) for a user. Specifying this parameter will convert a HKEY_CURRENT_USER registry key to the HKEY_USERS\$SID format. + Specify this parameter from the Invoke-HKCURegistrySettingsForAllUsers function to read/edit HKCU registry settings for all users on the system. +.PARAMETER DisableFunctionLogging + Disables logging of this function. Default: $true +.EXAMPLE + Convert-RegistryPath -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1AD147D0-BE0E-3D6C-AC11-64F6DC4163F1}' +.EXAMPLE + Convert-RegistryPath -Key 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1AD147D0-BE0E-3D6C-AC11-64F6DC4163F1}' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$SID, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [bool]$DisableFunctionLogging = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Convert the registry key hive to the full path, only match if at the beginning of the line + If ($Key -match '^HKLM') { + $Key = $Key -replace '^HKLM:\\', 'HKEY_LOCAL_MACHINE\' -replace '^HKLM:', 'HKEY_LOCAL_MACHINE\' -replace '^HKLM\\', 'HKEY_LOCAL_MACHINE\' + } + elseif ($Key -match '^HKCR') { + $Key = $Key -replace '^HKCR:\\', 'HKEY_CLASSES_ROOT\' -replace '^HKCR:', 'HKEY_CLASSES_ROOT\' -replace '^HKCR\\', 'HKEY_CLASSES_ROOT\' + } + elseif ($Key -match '^HKCU') { + $Key = $Key -replace '^HKCU:\\', 'HKEY_CURRENT_USER\' -replace '^HKCU:', 'HKEY_CURRENT_USER\' -replace '^HKCU\\', 'HKEY_CURRENT_USER\' + } + elseif ($Key -match '^HKU') { + $Key = $Key -replace '^HKU:\\', 'HKEY_USERS\' -replace '^HKU:', 'HKEY_USERS\' -replace '^HKU\\', 'HKEY_USERS\' + } + elseif ($Key -match '^HKCC') { + $Key = $Key -replace '^HKCC:\\', 'HKEY_CURRENT_CONFIG\' -replace '^HKCC:', 'HKEY_CURRENT_CONFIG\' -replace '^HKCC\\', 'HKEY_CURRENT_CONFIG\' + } + elseif ($Key -match '^HKPD') { + $Key = $Key -replace '^HKPD:\\', 'HKEY_PERFORMANCE_DATA\' -replace '^HKPD:', 'HKEY_PERFORMANCE_DATA\' -replace '^HKPD\\', 'HKEY_PERFORMANCE_DATA\' + } + + ## Append the PowerShell provider to the registry key path + If ($key -notmatch '^Registry::') {[string]$key = "Registry::$key" } + + If ($PSBoundParameters.ContainsKey('SID')) { + ## If the SID variable is specified, then convert all HKEY_CURRENT_USER key's to HKEY_USERS\$SID + If ($key -match '^Registry::HKEY_CURRENT_USER\\') { $key = $key -replace '^Registry::HKEY_CURRENT_USER\\', "Registry::HKEY_USERS\$SID\" } + Elseif (-not ($DisableFunctionLogging)) { + Write-Log -Message "SID parameter specified but the registry hive of the key is not HKEY_CURRENT_USER." -Source ${CmdletName} -Severity 2 + } + } + + If($Key -match '^Registry::HKEY_LOCAL_MACHINE|^Registry::HKEY_CLASSES_ROOT|^Registry::HKEY_CURRENT_USER|^Registry::HKEY_USERS|^Registry::HKEY_CURRENT_CONFIG|^Registry::HKEY_PERFORMANCE_DATA') { + ## Check for expected key string format + If (-not ($DisableFunctionLogging)) { + Write-Log -Message "Return fully qualified registry key path [$key]." -Source ${CmdletName} + } + Write-Output -InputObject $key + } + Else{ + # If key string is not properly formatted, throw an error + Throw "Unable to detect target registry hive in string [$key]." + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-RegistryValue +Function Test-RegistryValue { +<# +.SYNOPSIS + Test if a registry value exists. +.DESCRIPTION + Checks a registry key path to see if it has a value with a given name. Can correctly handle cases where a value simply has an empty or null value. +.PARAMETER Key + Path of the registry key. +.PARAMETER Value + Specify the registry key value to check the existence of. +.PARAMETER SID + The security identifier (SID) for a user. Specifying this parameter will convert a HKEY_CURRENT_USER registry key to the HKEY_USERS\$SID format. + Specify this parameter from the Invoke-HKCURegistrySettingsForAllUsers function to read/edit HKCU registry settings for all users on the system. +.EXAMPLE + Test-RegistryValue -Key 'HKLM:SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations' +.NOTES + To test if registry key exists, use Test-Path function like so: + Test-Path -Path $Key -PathType 'Container' +.LINK + http://psappdeploytoolkit.com +#> + Param ( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()]$Key, + [Parameter(Mandatory=$true,Position=1)] + [ValidateNotNullOrEmpty()]$Value, + [Parameter(Mandatory=$false,Position=2)] + [ValidateNotNullorEmpty()] + [string]$SID + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## If the SID variable is specified, then convert all HKEY_CURRENT_USER key's to HKEY_USERS\$SID + Try { + If ($PSBoundParameters.ContainsKey('SID')) { + [string]$Key = Convert-RegistryPath -Key $Key -SID $SID + } + Else { + [string]$Key = Convert-RegistryPath -Key $Key + } + } + Catch { + Throw + } + [boolean]$IsRegistryValueExists = $false + Try { + If (Test-Path -LiteralPath $Key -ErrorAction 'Stop') { + [string[]]$PathProperties = Get-Item -LiteralPath $Key -ErrorAction 'Stop' | Select-Object -ExpandProperty 'Property' -ErrorAction 'Stop' + If ($PathProperties -contains $Value) { $IsRegistryValueExists = $true } + } + } + Catch { } + + If ($IsRegistryValueExists) { + Write-Log -Message "Registry key value [$Key] [$Value] does exist." -Source ${CmdletName} + } + Else { + Write-Log -Message "Registry key value [$Key] [$Value] does not exist." -Source ${CmdletName} + } + Write-Output -InputObject $IsRegistryValueExists + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-RegistryKey +Function Get-RegistryKey { +<# +.SYNOPSIS + Retrieves value names and value data for a specified registry key or optionally, a specific value. +.DESCRIPTION + Retrieves value names and value data for a specified registry key or optionally, a specific value. + If the registry key does not exist or contain any values, the function will return $null by default. To test for existence of a registry key path, use built-in Test-Path cmdlet. +.PARAMETER Key + Path of the registry key. +.PARAMETER Value + Value to retrieve (optional). +.PARAMETER SID + The security identifier (SID) for a user. Specifying this parameter will convert a HKEY_CURRENT_USER registry key to the HKEY_USERS\$SID format. + Specify this parameter from the Invoke-HKCURegistrySettingsForAllUsers function to read/edit HKCU registry settings for all users on the system. +.PARAMETER ReturnEmptyKeyIfExists + Return the registry key if it exists but it has no property/value pairs underneath it. Default is: $false. +.PARAMETER DoNotExpandEnvironmentNames + Return unexpanded REG_EXPAND_SZ values. Default is: $false. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-RegistryKey -Key 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1AD147D0-BE0E-3D6C-AC11-64F6DC4163F1}' +.EXAMPLE + Get-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\iexplore.exe' +.EXAMPLE + Get-RegistryKey -Key 'HKLM:Software\Wow6432Node\Microsoft\Microsoft SQL Server Compact Edition\v3.5' -Value 'Version' +.EXAMPLE + Get-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Value 'Path' -DoNotExpandEnvironmentNames + Returns %ProgramFiles%\Java instead of C:\Program Files\Java +.EXAMPLE + Get-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Example' -Value '(Default)' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$Value, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$SID, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$ReturnEmptyKeyIfExists = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$DoNotExpandEnvironmentNames = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## If the SID variable is specified, then convert all HKEY_CURRENT_USER key's to HKEY_USERS\$SID + If ($PSBoundParameters.ContainsKey('SID')) { + [string]$key = Convert-RegistryPath -Key $key -SID $SID + } + Else { + [string]$key = Convert-RegistryPath -Key $key + } + + ## Check if the registry key exists + If (-not (Test-Path -LiteralPath $key -ErrorAction 'Stop')) { + Write-Log -Message "Registry key [$key] does not exist. Return `$null." -Severity 2 -Source ${CmdletName} + $regKeyValue = $null + } + Else { + If ($PSBoundParameters.ContainsKey('Value')) { + Write-Log -Message "Get registry key [$key] value [$value]." -Source ${CmdletName} + } + Else { + Write-Log -Message "Get registry key [$key] and all property values." -Source ${CmdletName} + } + + ## Get all property values for registry key + $regKeyValue = Get-ItemProperty -LiteralPath $key -ErrorAction 'Stop' + [int32]$regKeyValuePropertyCount = $regKeyValue | Measure-Object | Select-Object -ExpandProperty 'Count' + + ## Select requested property + If ($PSBoundParameters.ContainsKey('Value')) { + # Check if registry value exists + [boolean]$IsRegistryValueExists = $false + If ($regKeyValuePropertyCount -gt 0) { + Try { + [string[]]$PathProperties = Get-Item -LiteralPath $Key -ErrorAction 'Stop' | Select-Object -ExpandProperty 'Property' -ErrorAction 'Stop' + If ($PathProperties -contains $Value) { $IsRegistryValueExists = $true } + } + Catch { } + } + + # Get the Value (do not make a strongly typed variable because it depends entirely on what kind of value is being read) + If ($IsRegistryValueExists) { + If ($DoNotExpandEnvironmentNames) { #Only useful on 'ExpandString' values + If ($Value -like '(Default)') { + $regKeyValue = $(Get-Item -LiteralPath $key -ErrorAction 'Stop').GetValue($null,$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + } + Else { + $regKeyValue = $(Get-Item -LiteralPath $key -ErrorAction 'Stop').GetValue($Value,$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + } + } + ElseIf ($Value -like '(Default)') { + $regKeyValue = $(Get-Item -LiteralPath $key -ErrorAction 'Stop').GetValue($null) + } + Else { + $regKeyValue = $regKeyValue | Select-Object -ExpandProperty $Value -ErrorAction 'SilentlyContinue' + } + } + Else { + Write-Log -Message "Registry key value [$Key] [$Value] does not exist. Return `$null." -Source ${CmdletName} + $regKeyValue = $null + } + } + ## Select all properties or return empty key object + Else { + If ($regKeyValuePropertyCount -eq 0) { + If ($ReturnEmptyKeyIfExists) { + Write-Log -Message "No property values found for registry key. Return empty registry key object [$key]." -Source ${CmdletName} + $regKeyValue = Get-Item -LiteralPath $key -Force -ErrorAction 'Stop' + } + Else { + Write-Log -Message "No property values found for registry key. Return `$null." -Source ${CmdletName} + $regKeyValue = $null + } + } + } + } + Write-Output -InputObject ($regKeyValue) + } + Catch { + If (-not $Value) { + Write-Log -Message "Failed to read registry key [$key]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to read registry key [$key]: $($_.Exception.Message)" + } + } + Else { + Write-Log -Message "Failed to read registry key [$key] value [$value]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to read registry key [$key] value [$value]: $($_.Exception.Message)" + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-RegistryKey +Function Set-RegistryKey { +<# +.SYNOPSIS + Creates a registry key name, value, and value data; it sets the same if it already exists. +.DESCRIPTION + Creates a registry key name, value, and value data; it sets the same if it already exists. +.PARAMETER Key + The registry key path. +.PARAMETER Name + The value name. +.PARAMETER Value + The value data. +.PARAMETER Type + The type of registry value to create or set. Options: 'Binary','DWord','ExpandString','MultiString','None','QWord','String','Unknown'. Default: String. + Dword should be specified as a decimal. +.PARAMETER SID + The security identifier (SID) for a user. Specifying this parameter will convert a HKEY_CURRENT_USER registry key to the HKEY_USERS\$SID format. + Specify this parameter from the Invoke-HKCURegistrySettingsForAllUsers function to read/edit HKCU registry settings for all users on the system. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Set-RegistryKey -Key $blockedAppPath -Name 'Debugger' -Value $blockedAppDebuggerValue +.EXAMPLE + Set-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE' -Name 'Application' -Type 'Dword' -Value '1' +.EXAMPLE + Set-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name 'Debugger' -Value $blockedAppDebuggerValue -Type String +.EXAMPLE + Set-RegistryKey -Key 'HKCU\Software\Microsoft\Example' -Name 'Data' -Value (0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x02,0x02,0x02) -Type 'Binary' +.EXAMPLE + Set-RegistryKey -Key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Example' -Value '(Default)' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + $Value, + [Parameter(Mandatory=$false)] + [ValidateSet('Binary','DWord','ExpandString','MultiString','None','QWord','String','Unknown')] + [Microsoft.Win32.RegistryValueKind]$Type = 'String', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$SID, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + [string]$RegistryValueWriteAction = 'set' + + ## If the SID variable is specified, then convert all HKEY_CURRENT_USER key's to HKEY_USERS\$SID + If ($PSBoundParameters.ContainsKey('SID')) { + [string]$key = Convert-RegistryPath -Key $key -SID $SID + } + Else { + [string]$key = Convert-RegistryPath -Key $key + } + + ## Create registry key if it doesn't exist + If (-not (Test-Path -LiteralPath $key -ErrorAction 'Stop')) { + Try { + Write-Log -Message "Create registry key [$key]." -Source ${CmdletName} + # No forward slash found in Key. Use New-Item cmdlet to create registry key + If ((($Key -split '/').Count - 1) -eq 0) + { + $null = New-Item -Path $key -ItemType 'Registry' -Force -ErrorAction 'Stop' + } + # Forward slash was found in Key. Use REG.exe ADD to create registry key + Else + { + [string]$CreateRegkeyResult = & "$envWinDir\System32\reg.exe" Add "$($Key.Substring($Key.IndexOf('::') + 2))" + If ($global:LastExitCode -ne 0) + { + Throw "Failed to create registry key [$Key]" + } + } + } + Catch { + Throw + } + } + + If ($Name) { + ## Set registry value if it doesn't exist + If (-not (Get-ItemProperty -LiteralPath $key -Name $Name -ErrorAction 'SilentlyContinue')) { + Write-Log -Message "Set registry key value: [$key] [$name = $value]." -Source ${CmdletName} + $null = New-ItemProperty -LiteralPath $key -Name $name -Value $value -PropertyType $Type -ErrorAction 'Stop' + } + ## Update registry value if it does exist + Else { + [string]$RegistryValueWriteAction = 'update' + If ($Name -eq '(Default)') { + ## Set Default registry key value with the following workaround, because Set-ItemProperty contains a bug and cannot set Default registry key value + $null = $(Get-Item -LiteralPath $key -ErrorAction 'Stop').OpenSubKey('','ReadWriteSubTree').SetValue($null,$value) + } + Else { + Write-Log -Message "Update registry key value: [$key] [$name = $value]." -Source ${CmdletName} + $null = Set-ItemProperty -LiteralPath $key -Name $name -Value $value -ErrorAction 'Stop' + } + } + } + } + Catch { + If ($Name) { + Write-Log -Message "Failed to $RegistryValueWriteAction value [$value] for registry key [$key] [$name]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to $RegistryValueWriteAction value [$value] for registry key [$key] [$name]: $($_.Exception.Message)" + } + } + Else { + Write-Log -Message "Failed to set registry key [$key]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to set registry key [$key]: $($_.Exception.Message)" + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Remove-RegistryKey +Function Remove-RegistryKey { +<# +.SYNOPSIS + Deletes the specified registry key or value. +.DESCRIPTION + Deletes the specified registry key or value. +.PARAMETER Key + Path of the registry key to delete. +.PARAMETER Name + Name of the registry value to delete. +.PARAMETER Recurse + Delete registry key recursively. +.PARAMETER SID + The security identifier (SID) for a user. Specifying this parameter will convert a HKEY_CURRENT_USER registry key to the HKEY_USERS\$SID format. + Specify this parameter from the Invoke-HKCURegistrySettingsForAllUsers function to read/edit HKCU registry settings for all users on the system. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Remove-RegistryKey -Key 'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce' +.EXAMPLE + Remove-RegistryKey -Key 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name 'RunAppInstall' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [switch]$Recurse, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$SID, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## If the SID variable is specified, then convert all HKEY_CURRENT_USER key's to HKEY_USERS\$SID + If ($PSBoundParameters.ContainsKey('SID')) { + [string]$Key = Convert-RegistryPath -Key $Key -SID $SID + } + Else { + [string]$Key = Convert-RegistryPath -Key $Key + } + + If (-not ($Name)) { + If (Test-Path -LiteralPath $Key -ErrorAction 'Stop') { + If ($Recurse) { + Write-Log -Message "Delete registry key recursively [$Key]." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $Key -Force -Recurse -ErrorAction 'Stop' + } + Else { + If ($null -eq (Get-ChildItem -LiteralPath $Key -ErrorAction 'Stop')){ + ## Check if there are subkeys of $Key, if so, executing Remove-Item will hang. Avoiding this with Get-ChildItem. + Write-Log -Message "Delete registry key [$Key]." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $Key -Force -ErrorAction 'Stop' + } + Else { + Throw "Unable to delete child key(s) of [$Key] without [-Recurse] switch." + } + } + } + Else { + Write-Log -Message "Unable to delete registry key [$Key] because it does not exist." -Severity 2 -Source ${CmdletName} + } + } + Else { + If (Test-Path -LiteralPath $Key -ErrorAction 'Stop') { + Write-Log -Message "Delete registry value [$Key] [$Name]." -Source ${CmdletName} + + If ($Name -eq '(Default)') { + ## Remove (Default) registry key value with the following workaround because Remove-ItemProperty cannot remove the (Default) registry key value + $null = (Get-Item -LiteralPath $Key -ErrorAction 'Stop').OpenSubKey('','ReadWriteSubTree').DeleteValue('') + } + Else { + $null = Remove-ItemProperty -LiteralPath $Key -Name $Name -Force -ErrorAction 'Stop' + } + } + Else { + Write-Log -Message "Unable to delete registry value [$Key] [$Name] because registry key does not exist." -Severity 2 -Source ${CmdletName} + } + } + } + Catch [System.Management.Automation.PSArgumentException] { + Write-Log -Message "Unable to delete registry value [$Key] [$Name] because it does not exist." -Severity 2 -Source ${CmdletName} + } + Catch { + If (-not ($Name)) { + Write-Log -Message "Failed to delete registry key [$Key]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to delete registry key [$Key]: $($_.Exception.Message)" + } + } + Else { + Write-Log -Message "Failed to delete registry value [$Key] [$Name]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to delete registry value [$Key] [$Name]: $($_.Exception.Message)" + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Invoke-HKCURegistrySettingsForAllUsers +Function Invoke-HKCURegistrySettingsForAllUsers { +<# +.SYNOPSIS + Set current user registry settings for all current users and any new users in the future. +.DESCRIPTION + Set HKCU registry settings for all current and future users by loading their NTUSER.dat registry hive file, and making the modifications. + This function will modify HKCU settings for all users even when executed under the SYSTEM account. + To ensure new users in the future get the registry edits, the Default User registry hive used to provision the registry for new users is modified. + This function can be used as an alternative to using ActiveSetup for registry settings. + The advantage of using this function over ActiveSetup is that a user does not have to log off and log back on before the changes take effect. +.PARAMETER RegistrySettings + Script block which contains HKCU registry settings which should be modified for all users on the system. Must specify the -SID parameter for all HKCU settings. +.PARAMETER UserProfiles + Specify the user profiles to modify HKCU registry settings for. Default is all user profiles except for system profiles. +.EXAMPLE + [scriptblock]$HKCURegistrySettings = { + Set-RegistryKey -Key 'HKCU\Software\Microsoft\Office\14.0\Common' -Name 'qmenable' -Value 0 -Type DWord -SID $UserProfile.SID + Set-RegistryKey -Key 'HKCU\Software\Microsoft\Office\14.0\Common' -Name 'updatereliabilitydata' -Value 1 -Type DWord -SID $UserProfile.SID + } + Invoke-HKCURegistrySettingsForAllUsers -RegistrySettings $HKCURegistrySettings +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [scriptblock]$RegistrySettings, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [psobject[]]$UserProfiles = (Get-UserProfiles) + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ForEach ($UserProfile in $UserProfiles) { + Try { + # Set the path to the user's registry hive when it is loaded + [string]$UserRegistryPath = "Registry::HKEY_USERS\$($UserProfile.SID)" + + # Set the path to the user's registry hive file + [string]$UserRegistryHiveFile = Join-Path -Path $UserProfile.ProfilePath -ChildPath 'NTUSER.DAT' + + # Load the User profile registry hive if it is not already loaded because the User is logged in + [boolean]$ManuallyLoadedRegHive = $false + If (-not (Test-Path -LiteralPath $UserRegistryPath)) { + # Load the User registry hive if the registry hive file exists + If (Test-Path -LiteralPath $UserRegistryHiveFile -PathType 'Leaf') { + Write-Log -Message "Load the User [$($UserProfile.NTAccount)] registry hive in path [HKEY_USERS\$($UserProfile.SID)]." -Source ${CmdletName} + [string]$HiveLoadResult = & "$envWinDir\System32\reg.exe" load "`"HKEY_USERS\$($UserProfile.SID)`"" "`"$UserRegistryHiveFile`"" + + If ($global:LastExitCode -ne 0) { + Throw "Failed to load the registry hive for User [$($UserProfile.NTAccount)] with SID [$($UserProfile.SID)]. Failure message [$HiveLoadResult]. Continue..." + } + + [boolean]$ManuallyLoadedRegHive = $true + } + Else { + Throw "Failed to find the registry hive file [$UserRegistryHiveFile] for User [$($UserProfile.NTAccount)] with SID [$($UserProfile.SID)]. Continue..." + } + } + Else { + Write-Log -Message "The User [$($UserProfile.NTAccount)] registry hive is already loaded in path [HKEY_USERS\$($UserProfile.SID)]." -Source ${CmdletName} + } + + ## Execute ScriptBlock which contains code to manipulate HKCU registry. + # Make sure read/write calls to the HKCU registry hive specify the -SID parameter or settings will not be changed for all users. + # Example: Set-RegistryKey -Key 'HKCU\Software\Microsoft\Office\14.0\Common' -Name 'qmenable' -Value 0 -Type DWord -SID $UserProfile.SID + Write-Log -Message 'Execute ScriptBlock to modify HKCU registry settings for all users.' -Source ${CmdletName} + & $RegistrySettings + } + Catch { + Write-Log -Message "Failed to modify the registry hive for User [$($UserProfile.NTAccount)] with SID [$($UserProfile.SID)] `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + Finally { + If ($ManuallyLoadedRegHive) { + Try { + Write-Log -Message "Unload the User [$($UserProfile.NTAccount)] registry hive in path [HKEY_USERS\$($UserProfile.SID)]." -Source ${CmdletName} + [string]$HiveLoadResult = & "$envWinDir\System32\reg.exe" unload "`"HKEY_USERS\$($UserProfile.SID)`"" + + If ($global:LastExitCode -ne 0) { + Write-Log -Message "REG.exe failed to unload the registry hive and exited with exit code [$($global:LastExitCode)]. Performing manual garbage collection to ensure successful unloading of registry hive." -Severity 2 -Source ${CmdletName} + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + Start-Sleep -Seconds 5 + + Write-Log -Message "Unload the User [$($UserProfile.NTAccount)] registry hive in path [HKEY_USERS\$($UserProfile.SID)]." -Source ${CmdletName} + [string]$HiveLoadResult = & "$envWinDir\System32\reg.exe" unload "`"HKEY_USERS\$($UserProfile.SID)`"" + If ($global:LastExitCode -ne 0) { Throw "REG.exe failed with exit code [$($global:LastExitCode)] and result [$HiveLoadResult]." } + } + } + Catch { + Write-Log -Message "Failed to unload the registry hive for User [$($UserProfile.NTAccount)] with SID [$($UserProfile.SID)]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function ConvertTo-NTAccountOrSID +Function ConvertTo-NTAccountOrSID { +<# +.SYNOPSIS + Convert between NT Account names and their security identifiers (SIDs). +.DESCRIPTION + Specify either the NT Account name or the SID and get the other. Can also convert well known sid types. +.PARAMETER AccountName + The Windows NT Account name specified in \ format. + Use fully qualified account names (e.g., \) instead of isolated names (e.g, ) because they are unambiguous and provide better performance. +.PARAMETER SID + The Windows NT Account SID. +.PARAMETER WellKnownSIDName + Specify the Well Known SID name translate to the actual SID (e.g., LocalServiceSid). + To get all well known SIDs available on system: [enum]::GetNames([Security.Principal.WellKnownSidType]) +.PARAMETER WellKnownToNTAccount + Convert the Well Known SID to an NTAccount name +.EXAMPLE + ConvertTo-NTAccountOrSID -AccountName 'CONTOSO\User1' + Converts a Windows NT Account name to the corresponding SID +.EXAMPLE + ConvertTo-NTAccountOrSID -SID 'S-1-5-21-1220945662-2111687655-725345543-14012660' + Converts a Windows NT Account SID to the corresponding NT Account Name +.EXAMPLE + ConvertTo-NTAccountOrSID -WellKnownSIDName 'NetworkServiceSid' + Converts a Well Known SID name to a SID +.NOTES + This is an internal script function and should typically not be called directly. + The conversion can return an empty result if the user account does not exist anymore or if translation fails. + http://blogs.technet.com/b/askds/archive/2011/07/28/troubleshooting-sid-translation-failures-from-the-obvious-to-the-not-so-obvious.aspx +.LINK + http://psappdeploytoolkit.com + List of Well Known SIDs: http://msdn.microsoft.com/en-us/library/system.security.principal.wellknownsidtype(v=vs.110).aspx +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,ParameterSetName='NTAccountToSID',ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string]$AccountName, + [Parameter(Mandatory=$true,ParameterSetName='SIDToNTAccount',ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string]$SID, + [Parameter(Mandatory=$true,ParameterSetName='WellKnownName',ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [string]$WellKnownSIDName, + [Parameter(Mandatory=$false,ParameterSetName='WellKnownName')] + [ValidateNotNullOrEmpty()] + [switch]$WellKnownToNTAccount + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Switch ($PSCmdlet.ParameterSetName) { + 'SIDToNTAccount' { + [string]$msg = "the SID [$SID] to an NT Account name" + Write-Log -Message "Convert $msg." -Source ${CmdletName} + + $NTAccountSID = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList $SID + $NTAccount = $NTAccountSID.Translate([Security.Principal.NTAccount]) + Write-Output -InputObject $NTAccount + } + 'NTAccountToSID' { + [string]$msg = "the NT Account [$AccountName] to a SID" + Write-Log -Message "Convert $msg." -Source ${CmdletName} + + $NTAccount = New-Object -TypeName 'System.Security.Principal.NTAccount' -ArgumentList $AccountName + $NTAccountSID = $NTAccount.Translate([Security.Principal.SecurityIdentifier]) + Write-Output -InputObject $NTAccountSID + } + 'WellKnownName' { + If ($WellKnownToNTAccount) { + [string]$ConversionType = 'NTAccount' + } + Else { + [string]$ConversionType = 'SID' + } + [string]$msg = "the Well Known SID Name [$WellKnownSIDName] to a $ConversionType" + Write-Log -Message "Convert $msg." -Source ${CmdletName} + + # Get the SID for the root domain + Try { + $MachineRootDomain = (Get-WmiObject -Class 'Win32_ComputerSystem' -ErrorAction 'Stop').Domain.ToLower() + $ADDomainObj = New-Object -TypeName 'System.DirectoryServices.DirectoryEntry' -ArgumentList "LDAP://$MachineRootDomain" + $DomainSidInBinary = $ADDomainObj.ObjectSid + $DomainSid = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ($DomainSidInBinary[0], 0) + } + Catch { + Write-Log -Message 'Unable to get Domain SID from Active Directory. Setting Domain SID to $null.' -Severity 2 -Source ${CmdletName} + $DomainSid = $null + } + + # Get the SID for the well known SID name + $WellKnownSidType = [Security.Principal.WellKnownSidType]::$WellKnownSIDName + $NTAccountSID = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ($WellKnownSidType, $DomainSid) + + If ($WellKnownToNTAccount) { + $NTAccount = $NTAccountSID.Translate([Security.Principal.NTAccount]) + Write-Output -InputObject $NTAccount + } + Else { + Write-Output -InputObject $NTAccountSID + } + } + } + } + Catch { + Write-Log -Message "Failed to convert $msg. It may not be a valid account anymore or there is some other problem. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-UserProfiles +Function Get-UserProfiles { +<# +.SYNOPSIS + Get the User Profile Path, User Account Sid, and the User Account Name for all users that log onto the machine and also the Default User (which does not log on). +.DESCRIPTION + Get the User Profile Path, User Account Sid, and the User Account Name for all users that log onto the machine and also the Default User (which does not log on). + Please note that the NTAccount property may be empty for some user profiles but the SID and ProfilePath properties will always be populated. +.PARAMETER ExcludeNTAccount + Specify NT account names in Domain\Username format to exclude from the list of user profiles. +.PARAMETER ExcludeSystemProfiles + Exclude system profiles: SYSTEM, LOCAL SERVICE, NETWORK SERVICE. Default is: $true. +.PARAMETER ExcludeDefaultUser + Exclude the Default User. Default is: $false. +.EXAMPLE + Get-UserProfiles + Returns the following properties for each user profile on the system: NTAccount, SID, ProfilePath +.EXAMPLE + Get-UserProfiles -ExcludeNTAccount 'CONTOSO\Robot','CONTOSO\ntadmin' +.EXAMPLE + [string[]]$ProfilePaths = Get-UserProfiles | Select-Object -ExpandProperty 'ProfilePath' + Returns the user profile path for each user on the system. This information can then be used to make modifications under the user profile on the filesystem. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string[]]$ExcludeNTAccount, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ExcludeSystemProfiles = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$ExcludeDefaultUser = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Get the User Profile Path, User Account SID, and the User Account Name for all users that log onto the machine.' -Source ${CmdletName} + + ## Get the User Profile Path, User Account Sid, and the User Account Name for all users that log onto the machine + [string]$UserProfileListRegKey = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' + [psobject[]]$UserProfiles = Get-ChildItem -LiteralPath $UserProfileListRegKey -ErrorAction 'Stop' | + ForEach-Object { + Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction 'Stop' | Where-Object { ($_.ProfileImagePath) } | + Select-Object @{ Label = 'NTAccount'; Expression = { $(ConvertTo-NTAccountOrSID -SID $_.PSChildName).Value } }, @{ Label = 'SID'; Expression = { $_.PSChildName } }, @{ Label = 'ProfilePath'; Expression = { $_.ProfileImagePath } } + } | + Where-Object { $_.NTAccount } ## This removes "defaultuser0" account, which is Windows's 10 bug + If ($ExcludeSystemProfiles) { + [string[]]$SystemProfiles = 'S-1-5-18', 'S-1-5-19', 'S-1-5-20' + [psobject[]]$UserProfiles = $UserProfiles | Where-Object { $SystemProfiles -notcontains $_.SID } + } + If ($ExcludeNTAccount) { + [psobject[]]$UserProfiles = $UserProfiles | Where-Object { $ExcludeNTAccount -notcontains $_.NTAccount } + } + + ## Find the path to the Default User profile + If (-not $ExcludeDefaultUser) { + [string]$UserProfilesDirectory = Get-ItemProperty -LiteralPath $UserProfileListRegKey -Name 'ProfilesDirectory' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'ProfilesDirectory' + + # On Windows Vista or higher + If (([version]$envOSVersion).Major -gt 5) { + # Path to Default User Profile directory on Windows Vista or higher: By default, C:\Users\Default + [string]$DefaultUserProfileDirectory = Get-ItemProperty -LiteralPath $UserProfileListRegKey -Name 'Default' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'Default' + } + # On Windows XP or lower + Else { + # Default User Profile Name: By default, 'Default User' + [string]$DefaultUserProfileName = Get-ItemProperty -LiteralPath $UserProfileListRegKey -Name 'DefaultUserProfile' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'DefaultUserProfile' + + # Path to Default User Profile directory: By default, C:\Documents and Settings\Default User + [string]$DefaultUserProfileDirectory = Join-Path -Path $UserProfilesDirectory -ChildPath $DefaultUserProfileName + } + + ## Create a custom object for the Default User profile. + # Since the Default User is not an actual User account, it does not have a username or a SID. + # We will make up a SID and add it to the custom object so that we have a location to load the default registry hive into later on. + [psobject]$DefaultUserProfile = New-Object -TypeName 'PSObject' -Property @{ + NTAccount = 'Default User' + SID = 'S-1-5-21-Default-User' + ProfilePath = $DefaultUserProfileDirectory + } + + ## Add the Default User custom object to the User Profile list. + $UserProfiles += $DefaultUserProfile + } + + Write-Output -InputObject $UserProfiles + } + Catch { + Write-Log -Message "Failed to create a custom object representing all user profiles on the machine. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-FileVersion +Function Get-FileVersion { +<# +.SYNOPSIS + Gets the version of the specified file +.DESCRIPTION + Gets the version of the specified file +.PARAMETER File + Path of the file +.PARAMETER ProductVersion + Switch that makes the command return ProductVersion instead of FileVersion +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-FileVersion -File "$envProgramFilesX86\Adobe\Reader 11.0\Reader\AcroRd32.exe" +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$File, + [Parameter(Mandatory=$false)] + [switch]$ProductVersion, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Get version info for file [$file]." -Source ${CmdletName} + + If (Test-Path -LiteralPath $File -PathType 'Leaf') { + $fileVersionInfo = (Get-Command -Name $file -ErrorAction 'Stop').FileVersionInfo + If ($ProductVersion) { + $fileVersion = $fileVersionInfo.ProductVersion + } else { + $fileVersion = $fileVersionInfo.FileVersion + } + + If ($fileVersion) { + If ($ProductVersion) { + Write-Log -Message "Product version is [$fileVersion]." -Source ${CmdletName} + } + else + { + Write-Log -Message "File version is [$fileVersion]." -Source ${CmdletName} + } + + Write-Output -InputObject $fileVersion + } + Else { + Write-Log -Message 'No version information found.' -Source ${CmdletName} + } + } + Else { + Throw "File path [$file] does not exist." + } + } + Catch { + Write-Log -Message "Failed to get version info. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to get version info: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function New-Shortcut +Function New-Shortcut { +<# +.SYNOPSIS + Creates a new .lnk or .url type shortcut +.DESCRIPTION + Creates a new shortcut .lnk or .url file, with configurable options +.PARAMETER Path + Path to save the shortcut +.PARAMETER TargetPath + Target path or URL that the shortcut launches +.PARAMETER Arguments + Arguments to be passed to the target path +.PARAMETER IconLocation + Location of the icon used for the shortcut +.PARAMETER IconIndex + Executables, DLLs, ICO files with multiple icons need the icon index to be specified +.PARAMETER Description + Description of the shortcut +.PARAMETER WorkingDirectory + Working Directory to be used for the target path +.PARAMETER WindowStyle + Windows style of the application. Options: Normal, Maximized, Minimized. Default is: Normal. +.PARAMETER RunAsAdmin + Set shortcut to run program as administrator. This option will prompt user to elevate when executing shortcut. +.PARAMETER Hotkey + Create a Hotkey to launch the shortcut, e.g. "CTRL+SHIFT+F" +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + New-Shortcut -Path "$envProgramData\Microsoft\Windows\Start Menu\My Shortcut.lnk" -TargetPath "$envWinDir\system32\notepad.exe" -IconLocation "$envWinDir\system32\notepad.exe" -Description 'Notepad' -WorkingDirectory "$envHomeDrive\$envHomePath" +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Path, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$TargetPath, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$Arguments, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$IconLocation, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$IconIndex, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$Description, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$WorkingDirectory, + [Parameter(Mandatory=$false)] + [ValidateSet('Normal','Maximized','Minimized')] + [string]$WindowStyle, + [Parameter(Mandatory=$false)] + [switch]$RunAsAdmin, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Hotkey, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + If (-not $Shell) { [__comobject]$Shell = New-Object -ComObject 'WScript.Shell' -ErrorAction 'Stop' } + } + Process { + Try { + Try { + [IO.FileInfo]$Path = [IO.FileInfo]$Path + [string]$PathDirectory = $Path.DirectoryName + + If (-not (Test-Path -LiteralPath $PathDirectory -PathType 'Container' -ErrorAction 'Stop')) { + Write-Log -Message "Create shortcut directory [$PathDirectory]." -Source ${CmdletName} + $null = New-Item -Path $PathDirectory -ItemType 'Directory' -Force -ErrorAction 'Stop' + } + } + Catch { + Write-Log -Message "Failed to create shortcut directory [$PathDirectory]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + Throw + } + + Write-Log -Message "Create shortcut [$($path.FullName)]." -Source ${CmdletName} + If (($path.FullName).ToLower().EndsWith('.url')) { + [string[]]$URLFile = '[InternetShortcut]' + $URLFile += "URL=$targetPath" + If ($iconIndex) { $URLFile += "IconIndex=$iconIndex" } + If ($IconLocation) { $URLFile += "IconFile=$iconLocation" } + $URLFile | Out-File -FilePath $path.FullName -Force -Encoding 'default' -ErrorAction 'Stop' + } + ElseIf (($path.FullName).ToLower().EndsWith('.lnk')) { + If (($iconLocation -and $iconIndex) -and (-not ($iconLocation.Contains(',')))) { + $iconLocation = $iconLocation + ",$iconIndex" + } + Switch ($windowStyle) { + 'Normal' { $windowStyleInt = 1 } + 'Maximized' { $windowStyleInt = 3 } + 'Minimized' { $windowStyleInt = 7 } + Default { $windowStyleInt = 1 } + } + $shortcut = $shell.CreateShortcut($path.FullName) + $shortcut.TargetPath = $targetPath + $shortcut.Arguments = $arguments + $shortcut.Description = $description + $shortcut.WorkingDirectory = $workingDirectory + $shortcut.WindowStyle = $windowStyleInt + If ($hotkey) {$shortcut.Hotkey = $hotkey} + If ($iconLocation) { $shortcut.IconLocation = $iconLocation } + $shortcut.Save() + + ## Set shortcut to run program as administrator + If ($RunAsAdmin) { + Write-Log -Message 'Set shortcut to run program as administrator.' -Source ${CmdletName} + $TempFileName = [IO.Path]::GetRandomFileName() + $TempFile = [IO.FileInfo][IO.Path]::Combine($Path.Directory, $TempFileName) + $Writer = New-Object -TypeName 'System.IO.FileStream' -ArgumentList ($TempFile, ([IO.FileMode]::Create)) -ErrorAction 'Stop' + $Reader = $Path.OpenRead() + While ($Reader.Position -lt $Reader.Length) { + $Byte = $Reader.ReadByte() + If ($Reader.Position -eq 22) { $Byte = 34 } + $Writer.WriteByte($Byte) + } + $Reader.Close() + $Writer.Close() + $Path.Delete() + $null = Rename-Item -Path $TempFile -NewName $Path.Name -Force -ErrorAction 'Stop' + } + } + } + Catch { + Write-Log -Message "Failed to create shortcut [$($path.FullName)]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to create shortcut [$($path.FullName)]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Execute-ProcessAsUser +Function Execute-ProcessAsUser { +<# +.SYNOPSIS + Execute a process with a logged in user account, by using a scheduled task, to provide interaction with user in the SYSTEM context. +.DESCRIPTION + Execute a process with a logged in user account, by using a scheduled task, to provide interaction with user in the SYSTEM context. +.PARAMETER UserName + Logged in Username under which to run the process from. Default is: The active console user. If no console user exists but users are logged in, such as on terminal servers, then the first logged-in non-console user. +.PARAMETER Path + Path to the file being executed. +.PARAMETER Parameters + Arguments to be passed to the file being executed. +.PARAMETER SecureParameters + Hides all parameters passed to the executable from the Toolkit log file. +.PARAMETER RunLevel + Specifies the level of user rights that Task Scheduler uses to run the task. The acceptable values for this parameter are: + - HighestAvailable: Tasks run by using the highest available privileges (Admin privileges for Administrators). Default Value. + - LeastPrivilege: Tasks run by using the least-privileged user account (LUA) privileges. +.PARAMETER Wait + Wait for the process, launched by the scheduled task, to complete execution before accepting more input. Default is $false. +.PARAMETER PassThru + Returns the exit code from this function or the process launched by the scheduled task. +.PARAMETER WorkingDirectory + Set working directory for the process. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is $true. +.EXAMPLE + Execute-ProcessAsUser -UserName 'CONTOSO\User' -Path "$PSHOME\powershell.exe" -Parameters "-Command & { & `"C:\Test\Script.ps1`"; Exit `$LastExitCode }" -Wait + Execute process under a user account by specifying a username under which to execute it. +.EXAMPLE + Execute-ProcessAsUser -Path "$PSHOME\powershell.exe" -Parameters "-Command & { & `"C:\Test\Script.ps1`"; Exit `$LastExitCode }" -Wait + Execute process under a user account by using the default active logged in user that was detected when the toolkit was launched. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$UserName = $RunAsActiveUser.NTAccount, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Path, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Parameters = '', + [Parameter(Mandatory=$false)] + [switch]$SecureParameters = $false, + [Parameter(Mandatory=$false)] + [ValidateSet('HighestAvailable','LeastPrivilege')] + [string]$RunLevel = 'HighestAvailable', + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$Wait = $false, + [Parameter(Mandatory=$false)] + [switch]$PassThru = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$WorkingDirectory, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + [string]$executeAsUserTempPath = Join-Path -Path $dirAppDeployTemp -ChildPath 'ExecuteAsUser' + } + Process { + ## Initialize exit code variable + [int32]$executeProcessAsUserExitCode = 0 + + ## Confirm that the username field is not empty + If (-not $UserName) { + [int32]$executeProcessAsUserExitCode = 60009 + Write-Log -Message "The function [${CmdletName}] has a -UserName parameter that has an empty default value because no logged in users were detected when the toolkit was launched." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "The function [${CmdletName}] has a -UserName parameter that has an empty default value because no logged in users were detected when the toolkit was launched." + } + Return + } + + ## Confirm if the toolkit is running with administrator privileges + If (($RunLevel -eq 'HighestAvailable') -and (-not $IsAdmin)) { + [int32]$executeProcessAsUserExitCode = 60003 + Write-Log -Message "The function [${CmdletName}] requires the toolkit to be running with Administrator privileges if the [-RunLevel] parameter is set to 'HighestAvailable'." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "The function [${CmdletName}] requires the toolkit to be running with Administrator privileges if the [-RunLevel] parameter is set to 'HighestAvailable'." + } + Return + } + + ## Check whether the specified Working Directory exists + If ($WorkingDirectory -and (-not (Test-Path -LiteralPath $WorkingDirectory -PathType 'Container'))) { + Write-Log -Message "The specified working directory does not exist or is not a directory. The scheduled task might not work as expected." -Severity 2 -Source ${CmdletName} + } + + ## Build the scheduled task XML name + [string]$schTaskName = "$appDeployToolkitName-ExecuteAsUser" + + ## Remove and recreate the temporary folder + If (Test-Path -LiteralPath $executeAsUserTempPath -PathType 'Container') { + Write-Log -Message "Previous [$executeAsUserTempPath] found. Attempting removal." -Source ${CmdletName} + Remove-Folder -Path $executeAsUserTempPath + } + Write-Log -Message "Creating [$executeAsUserTempPath]." -Source ${CmdletName} + Try { + $null = New-Item -Path $executeAsUserTempPath -ItemType 'Directory' -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Unable to create [$executeAsUserTempPath]. Possible attempt to gain elevated rights." -Source ${CmdletName} -Severity 2 + } + + ## If PowerShell.exe is being launched, then create a VBScript to launch PowerShell so that we can suppress the console window that flashes otherwise + If (((Split-Path -Path $Path -Leaf) -like 'PowerShell*') -or ((Split-Path -Path $Path -Leaf) -like 'cmd*')) { + If ($SecureParameters) { + Write-Log -Message "Preparing a vbs script that will start [$Path] (Parameters Hidden) as the logged-on user [$userName] silently..." -Source ${CmdletName} + } + Else { + Write-Log -Message "Preparing a vbs script that will start [$Path $Parameters] as the logged-on user [$userName] silently..." -Source ${CmdletName} + } + # Permit inclusion of double quotes in parameters + $QuotesIndex = $Parameters.Length - 1 + If ($QuotesIndex -lt 0) { + $QuotesIndex = 0 + } + + If ($($Parameters.Substring($QuotesIndex)) -eq '"') { + [string]$executeProcessAsUserParametersVBS = 'chr(34) & ' + "`"$($Path)`"" + ' & chr(34) & ' + '" ' + ($Parameters -replace "`r`n", ';' -replace "`n", ';' -replace '"', "`" & chr(34) & `"" -replace ' & chr\(34\) & "$', '') + ' & chr(34)' } + Else { + [string]$executeProcessAsUserParametersVBS = 'chr(34) & ' + "`"$($Path)`"" + ' & chr(34) & ' + '" ' + ($Parameters -replace "`r`n", ';' -replace "`n", ';' -replace '"', "`" & chr(34) & `"" -replace ' & chr\(34\) & "$','') + '"' } + [string[]]$executeProcessAsUserScript = "strCommand = $executeProcessAsUserParametersVBS" + $executeProcessAsUserScript += 'set oWShell = CreateObject("WScript.Shell")' + $executeProcessAsUserScript += 'intReturn = oWShell.Run(strCommand, 0, true)' + $executeProcessAsUserScript += 'WScript.Quit intReturn' + $executeProcessAsUserScript | Out-File -FilePath "$executeAsUserTempPath\$($schTaskName).vbs" -Force -Encoding 'default' -ErrorAction 'SilentlyContinue' + $Path = "$envWinDir\System32\wscript.exe" + $Parameters = "`"$executeAsUserTempPath\$($schTaskName).vbs`"" + + try { + Set-ItemPermission -Path "$executeAsUserTempPath\$schTaskName.vbs" -User $UserName -Permission 'Read' + } + catch { + Write-Log -Message "Failed to set read permissions on path [$executeAsUserTempPath\$schTaskName.vbs]. The function might not be able to work correctly." -Source ${CmdletName} -Severity 2 + } + } + + ## Prepare working directory insert + [string]$WorkingDirectoryInsert = "" + If ($WorkingDirectory) { + $WorkingDirectoryInsert = "`n $WorkingDirectory" + } + ## Specify the scheduled task configuration in XML format + [string]$xmlSchTask = @" + + + + + + StopExisting + false + false + true + false + false + + false + false + + true + true + false + false + false + PT72H + 7 + + + + $Path + $Parameters$WorkingDirectoryInsert + + + + + $UserName + InteractiveToken + $RunLevel + + + +"@ + ## Export the XML to file + Try { + # Specify the filename to export the XML to + [string]$xmlSchTaskFilePath = "$dirAppDeployTemp\$schTaskName.xml" + [string]$xmlSchTask | Out-File -FilePath $xmlSchTaskFilePath -Force -ErrorAction 'Stop' + Set-ItemPermission -Path $xmlSchTaskFilePath -User $UserName -Permission 'Read' + } + Catch { + [int32]$executeProcessAsUserExitCode = 60007 + Write-Log -Message "Failed to export the scheduled task XML file [$xmlSchTaskFilePath]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to export the scheduled task XML file [$xmlSchTaskFilePath]: $($_.Exception.Message)" + } + Return + } + + ## Create Scheduled Task to run the process with a logged-on user account + If ($Parameters) { + If ($SecureParameters) { + Write-Log -Message "Creating scheduled task to run the process [$Path] (Parameters Hidden) as the logged-on user [$userName]..." -Source ${CmdletName} + } + Else { + Write-Log -Message "Creating scheduled task to run the process [$Path $Parameters] as the logged-on user [$userName]..." -Source ${CmdletName} + } + } + Else { + Write-Log -Message "Creating scheduled task to run the process [$Path] as the logged-on user [$userName]..." -Source ${CmdletName} + } + [psobject]$schTaskResult = Execute-Process -Path $exeSchTasks -Parameters "/create /f /tn $schTaskName /xml `"$xmlSchTaskFilePath`"" -WindowStyle 'Hidden' -CreateNoWindow -PassThru -ExitOnProcessFailure $false + If ($schTaskResult.ExitCode -ne 0) { + [int32]$executeProcessAsUserExitCode = $schTaskResult.ExitCode + Write-Log -Message "Failed to create the scheduled task by importing the scheduled task XML file [$xmlSchTaskFilePath]." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to create the scheduled task by importing the scheduled task XML file [$xmlSchTaskFilePath]." + } + Return + } + + ## Trigger the Scheduled Task + If ($Parameters) { + If ($SecureParameters) { + Write-Log -Message "Trigger execution of scheduled task with command [$Path] (Parameters Hidden) as the logged-on user [$userName]..." -Source ${CmdletName} + } + Else { + Write-Log -Message "Trigger execution of scheduled task with command [$Path $Parameters] as the logged-on user [$userName]..." -Source ${CmdletName} + } + } + Else { + Write-Log -Message "Trigger execution of scheduled task with command [$Path] as the logged-on user [$userName]..." -Source ${CmdletName} + } + [psobject]$schTaskResult = Execute-Process -Path $exeSchTasks -Parameters "/run /i /tn $schTaskName" -WindowStyle 'Hidden' -CreateNoWindow -Passthru -ExitOnProcessFailure $false + If ($schTaskResult.ExitCode -ne 0) { + [int32]$executeProcessAsUserExitCode = $schTaskResult.ExitCode + Write-Log -Message "Failed to trigger scheduled task [$schTaskName]." -Severity 3 -Source ${CmdletName} + # Delete Scheduled Task + Write-Log -Message 'Delete the scheduled task which did not trigger.' -Source ${CmdletName} + Execute-Process -Path $exeSchTasks -Parameters "/delete /tn $schTaskName /f" -WindowStyle 'Hidden' -CreateNoWindow -ExitOnProcessFailure $false + If (-not $ContinueOnError) { + Throw "Failed to trigger scheduled task [$schTaskName]." + } + Return + } + + ## Wait for the process launched by the scheduled task to complete execution + If ($Wait) { + Write-Log -Message "Waiting for the process launched by the scheduled task [$schTaskName] to complete execution (this may take some time)..." -Source ${CmdletName} + Start-Sleep -Seconds 1 + #If on Windows Vista or higer, Windows Task Scheduler 2.0 is supported. 'Schedule.Service' ComObject output is UI language independent + If (([version]$envOSVersion).Major -gt 5) { + Try { + [__comobject]$ScheduleService = New-Object -ComObject 'Schedule.Service' -ErrorAction Stop + $ScheduleService.Connect() + $RootFolder = $ScheduleService.GetFolder('\') + $Task = $RootFolder.GetTask("$schTaskName") + # Task State(Status) 4 = 'Running' + While ($Task.State -eq 4) { + Start-Sleep -Seconds 5 + } + # Get the exit code from the process launched by the scheduled task + [int32]$executeProcessAsUserExitCode = $Task.LastTaskResult + } + Catch { + Write-Log -Message "Failed to retrieve information from Task Scheduler. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + Finally { + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($ScheduleService) } Catch { } + } + } + #Windows Task Scheduler 1.0 + Else { + While ((($exeSchTasksResult = & $exeSchTasks /query /TN $schTaskName /V /FO CSV) | ConvertFrom-CSV | Select-Object -ExpandProperty 'Status' | Select-Object -First 1) -eq 'Running') { + Start-Sleep -Seconds 5 + } + # Get the exit code from the process launched by the scheduled task + [int32]$executeProcessAsUserExitCode = ($exeSchTasksResult = & $exeSchTasks /query /TN $schTaskName /V /FO CSV) | ConvertFrom-CSV | Select-Object -ExpandProperty 'Last Result' | Select-Object -First 1 + } + Write-Log -Message "Exit code from process launched by scheduled task [$executeProcessAsUserExitCode]." -Source ${CmdletName} + } + Else { + Start-Sleep -Seconds 1 + } + + ## Delete scheduled task + Try { + Write-Log -Message "Delete scheduled task [$schTaskName]." -Source ${CmdletName} + Execute-Process -Path $exeSchTasks -Parameters "/delete /tn $schTaskName /f" -WindowStyle 'Hidden' -CreateNoWindow -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to delete scheduled task [$schTaskName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Remove the XML scheduled task file + If (Test-Path -LiteralPath $xmlSchTaskFilePath -PathType 'Leaf') { + Remove-File -Path $xmlSchTaskFilePath + } + + ## Remove the temporary folder + If (Test-Path -LiteralPath $executeAsUserTempPath -PathType 'Container') { + Remove-Folder -Path $executeAsUserTempPath + } + } + End { + If ($PassThru) { Write-Output -InputObject $executeProcessAsUserExitCode } + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Update-Desktop +Function Update-Desktop { +<# +.SYNOPSIS + Refresh the Windows Explorer Shell, which causes the desktop icons and the environment variables to be reloaded. +.DESCRIPTION + Refresh the Windows Explorer Shell, which causes the desktop icons and the environment variables to be reloaded. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Update-Desktop +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Refresh the Desktop and the Windows Explorer environment process block.' -Source ${CmdletName} + [PSADT.Explorer]::RefreshDesktopAndEnvironmentVariables() + } + Catch { + Write-Log -Message "Failed to refresh the Desktop and the Windows Explorer environment process block. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to refresh the Desktop and the Windows Explorer environment process block: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +Set-Alias -Name 'Refresh-Desktop' -Value 'Update-Desktop' -Scope 'Script' -Force -ErrorAction 'SilentlyContinue' +#endregion + + +#region Function Update-SessionEnvironmentVariables +Function Update-SessionEnvironmentVariables { +<# +.SYNOPSIS + Updates the environment variables for the current PowerShell session with any environment variable changes that may have occurred during script execution. +.DESCRIPTION + Environment variable changes that take place during script execution are not visible to the current PowerShell session. + Use this function to refresh the current PowerShell session with all environment variable settings. +.PARAMETER LoadLoggedOnUserEnvironmentVariables + If script is running in SYSTEM context, this option allows loading environment variables from the active console user. If no console user exists but users are logged in, such as on terminal servers, then the first logged-in non-console user. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Update-SessionEnvironmentVariables +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$LoadLoggedOnUserEnvironmentVariables = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + [scriptblock]$GetEnvironmentVar = { + Param ( + $Key, + $Scope + ) + [Environment]::GetEnvironmentVariable($Key, $Scope) + } + } + Process { + Try { + Write-Log -Message 'Refresh the environment variables for this PowerShell session.' -Source ${CmdletName} + + If ($LoadLoggedOnUserEnvironmentVariables -and $RunAsActiveUser) { + [string]$CurrentUserEnvironmentSID = $RunAsActiveUser.SID + } + Else { + [string]$CurrentUserEnvironmentSID = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value + } + [string]$MachineEnvironmentVars = 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + [string]$UserEnvironmentVars = "Registry::HKEY_USERS\$CurrentUserEnvironmentSID\Environment" + + ## Update all session environment variables. Ordering is important here: $UserEnvironmentVars comes second so that we can override $MachineEnvironmentVars. + $MachineEnvironmentVars, $UserEnvironmentVars | Get-Item | Where-Object { $_ } | ForEach-Object { $envRegPath = $_.PSPath; $_ | Select-Object -ExpandProperty 'Property' | ForEach-Object { Set-Item -LiteralPath "env:$($_)" -Value (Get-ItemProperty -LiteralPath $envRegPath -Name $_).$_ } } + + ## Set PATH environment variable separately because it is a combination of the user and machine environment variables + [string[]]$PathFolders = 'Machine', 'User' | ForEach-Object { (& $GetEnvironmentVar -Key 'PATH' -Scope $_) } | Where-Object { $_ } | ForEach-Object { $_.Trim(';') } | ForEach-Object { $_.Split(';') } | ForEach-Object { $_.Trim() } | ForEach-Object { $_.Trim('"') } | Select-Object -Unique + $env:PATH = $PathFolders -join ';' + } + Catch { + Write-Log -Message "Failed to refresh the environment variables for this PowerShell session. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to refresh the environment variables for this PowerShell session: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +Set-Alias -Name 'Refresh-SessionEnvironmentVariables' -Value 'Update-SessionEnvironmentVariables' -Scope 'Script' -Force -ErrorAction 'SilentlyContinue' +#endregion + + +#region Function Get-SchedulerTask +Function Get-SchedulerTask { +<# +.SYNOPSIS + Retrieve all details for scheduled tasks on the local computer. +.DESCRIPTION + Retrieve all details for scheduled tasks on the local computer using schtasks.exe. All property names have spaces and colons removed. +.PARAMETER TaskName + Specify the name of the scheduled task to retrieve details for. Uses regex match to find scheduled task. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default: $true. +.EXAMPLE + Get-SchedulerTask + To display a list of all scheduled task properties. +.EXAMPLE + Get-SchedulerTask | Out-GridView + To display a grid view of all scheduled task properties. +.EXAMPLE + Get-SchedulerTask | Select-Object -Property TaskName + To display a list of all scheduled task names. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$TaskName, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + [psobject[]]$ScheduledTasks = @() + } + Process { + Try { + Write-Log -Message 'Retrieve Scheduled Tasks...' -Source ${CmdletName} + [string[]]$exeSchtasksResults = & $exeSchTasks /Query /V /FO CSV + If ($global:LastExitCode -ne 0) { Throw "Failed to retrieve scheduled tasks using [$exeSchTasks]." } + [psobject[]]$SchtasksResults = $exeSchtasksResults | ConvertFrom-CSV -Header 'HostName', 'TaskName', 'Next Run Time', 'Status', 'Logon Mode', 'Last Run Time', 'Last Result', 'Author', 'Task To Run', 'Start In', 'Comment', 'Scheduled Task State', 'Idle Time', 'Power Management', 'Run As User', 'Delete Task If Not Rescheduled', 'Stop Task If Runs X Hours and X Mins', 'Schedule', 'Schedule Type', 'Start Time', 'Start Date', 'End Date', 'Days', 'Months', 'Repeat: Every', 'Repeat: Until: Time', 'Repeat: Until: Duration', 'Repeat: Stop If Still Running' -ErrorAction 'Stop' + + If ($SchtasksResults) { + ForEach ($SchtasksResult in $SchtasksResults) { + If ($SchtasksResult.TaskName -match $TaskName) { + $SchtasksResult | Get-Member -MemberType 'Properties' | + ForEach-Object -Begin { + [hashtable]$Task = @{} + } -Process { + ## Remove spaces and colons in property names. Do not set property value if line being processed is a column header (this will only work on English language machines). + ($Task.($($_.Name).Replace(' ','').Replace(':',''))) = If ($_.Name -ne $SchtasksResult.($_.Name)) { $SchtasksResult.($_.Name) } + } -End { + ## Only add task to the custom object if all property values are not empty + If (($Task.Values | Select-Object -Unique | Measure-Object).Count) { + $ScheduledTasks += New-Object -TypeName 'PSObject' -Property $Task + } + } + } + } + } + } + Catch { + Write-Log -Message "Failed to retrieve scheduled tasks. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to retrieve scheduled tasks: $($_.Exception.Message)" + } + } + } + End { + Write-Output -InputObject $ScheduledTasks + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +# If Get-ScheduledTask doesn't exist, add alias Get-ScheduledTask +If (-not (Get-Command -Name "Get-ScheduledTask" -ErrorAction SilentlyContinue)) { + New-Alias -Name "Get-ScheduledTask" -Value "Get-SchedulerTask" +} +#endregion + + +#region Function Block-AppExecution +Function Block-AppExecution { +<# +.SYNOPSIS + Block the execution of an application(s) +.DESCRIPTION + This function is called when you pass the -BlockExecution parameter to the Stop-RunningApplications function. It does the following: + 1. Makes a copy of this script in a temporary directory on the local machine. + 2. Checks for an existing scheduled task from previous failed installation attempt where apps were blocked and if found, calls the Unblock-AppExecution function to restore the original IFEO registry keys. + This is to prevent the function from overriding the backup of the original IFEO options. + 3. Creates a scheduled task to restore the IFEO registry key values in case the script is terminated uncleanly by calling the local temporary copy of this script with the parameter -CleanupBlockedApps. + 4. Modifies the "Image File Execution Options" registry key for the specified process(s) to call this script with the parameter -ShowBlockedAppDialog. + 5. When the script is called with those parameters, it will display a custom message to the user to indicate that execution of the application has been blocked while the installation is in progress. + The text of this message can be customized in the XML configuration file. +.PARAMETER ProcessName + Name of the process or processes separated by commas +.EXAMPLE + Block-AppExecution -ProcessName ('winword','excel') +.NOTES + This is an internal script function and should typically not be called directly. + It is used when the -BlockExecution parameter is specified with the Show-InstallationWelcome function to block applications. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ## Specify process names separated by commas + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string[]]$ProcessName + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Remove illegal characters from the scheduled task arguments string + [char[]]$invalidScheduledTaskChars = '$', '!', '''', '"', '(', ')', ';', '\', '`', '*', '?', '{', '}', '[', ']', '<', '>', '|', '&', '%', '#', '~', '@', ' ' + [string]$SchInstallName = $installName + ForEach ($invalidChar in $invalidScheduledTaskChars) { [string]$SchInstallName = $SchInstallName -replace [regex]::Escape($invalidChar),'' } + [string]$blockExecutionTempPath = Join-Path -Path $dirAppDeployTemp -ChildPath 'BlockExecution' + [string]$schTaskUnblockAppsCommand += "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -File `"$blockExecutionTempPath\$scriptFileName`" -CleanupBlockedApps -ReferredInstallName `"$SchInstallName`" -ReferredInstallTitle `"$installTitle`" -ReferredLogName `"$logName`" -AsyncToolkitLaunch" + ## Specify the scheduled task configuration in XML format + [string]$xmlUnblockAppsSchTask = @" + + + + + + true + + + + + S-1-5-18 + + + + IgnoreNew + false + false + true + false + false + + false + false + + true + true + false + false + false + PT1H + 7 + + + + $PSHome\powershell.exe + $schTaskUnblockAppsCommand + + + +"@ + } + Process { + ## Bypass if no Admin rights + If ($configToolkitRequireAdmin -eq $false) { + Write-Log -Message "Bypassing Function [${CmdletName}], because [Require Admin: $configToolkitRequireAdmin]." -Source ${CmdletName} + Return + } + ## Bypass if in NonInteractive mode + If ($deployModeNonInteractive) { + Write-Log -Message "Bypassing Function [${CmdletName}], because [Mode: $deployMode]." -Source ${CmdletName} + Return + } + + [string]$schTaskBlockedAppsName = $installName + '_BlockedApps' + + ## Delete this file if it exists as it can cause failures (it is a bug from an older version of the toolkit) + If (Test-Path -LiteralPath "$configToolkitTempPath\PSAppDeployToolkit" -PathType 'Leaf' -ErrorAction 'SilentlyContinue') { + $null = Remove-Item -LiteralPath "$configToolkitTempPath\PSAppDeployToolkit" -Force -ErrorAction 'SilentlyContinue' + } + + If (Test-Path -LiteralPath $blockExecutionTempPath -PathType 'Container') { + Remove-Folder -Path $blockExecutionTempPath + } + + Try { + $null = New-Item -Path $blockExecutionTempPath -ItemType 'Directory' -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Unable to create [$blockExecutionTempPath]. Possible attempt to gain elevated rights." -Source ${CmdletName} + } + + Copy-Item -Path "$scriptRoot\*.*" -Destination $blockExecutionTempPath -Exclude 'thumbs.db' -Force -Recurse -ErrorAction 'SilentlyContinue' + + ## Build the debugger block value script + [string]$debuggerBlockMessageCmd = "`"$PSHome\powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -File `" & chr(34) & `"$blockExecutionTempPath\$scriptFileName`" & chr(34) & `" -ShowBlockedAppDialog -AsyncToolkitLaunch -ReferredInstallTitle `" & chr(34) & `"$installTitle`" & chr(34)" + [string[]]$debuggerBlockScript = "strCommand = $debuggerBlockMessageCmd" + $debuggerBlockScript += 'set oWShell = CreateObject("WScript.Shell")' + $debuggerBlockScript += 'oWShell.Run strCommand, 0, false' + $debuggerBlockScript | Out-File -FilePath "$blockExecutionTempPath\AppDeployToolkit_BlockAppExecutionMessage.vbs" -Force -Encoding 'default' -ErrorAction 'SilentlyContinue' + [string]$debuggerBlockValue = "$envWinDir\System32\wscript.exe `"$blockExecutionTempPath\AppDeployToolkit_BlockAppExecutionMessage.vbs`"" + + ## Set contents to be readable for all users (BUILTIN\USERS) + try { + $Users = ConvertTo-NTAccountOrSID -SID "S-1-5-32-545" + Set-ItemPermission -Path $blockExecutionTempPath -User $Users -Permission 'Read' -Inheritance "ObjectInherit","ContainerInherit" + } + catch { + Write-Log -Message "Failed to set read permissions on path [$blockExecutionTempPath]. The function might not be able to work correctly." -Source ${CmdletName} -Severity 2 + } + + ## Create a scheduled task to run on startup to call this script and clean up blocked applications in case the installation is interrupted, e.g. user shuts down during installation" + Write-Log -Message 'Create scheduled task to cleanup blocked applications in case installation is interrupted.' -Source ${CmdletName} + If (Get-SchedulerTask -ContinueOnError $true | Select-Object -Property 'TaskName' | Where-Object { $_.TaskName -eq "\$schTaskBlockedAppsName" }) { + Write-Log -Message "Scheduled task [$schTaskBlockedAppsName] already exists." -Source ${CmdletName} + } + Else { + ## Export the scheduled task XML to file + Try { + ## Specify the filename to export the XML to + ## XML does not need to be user readable to stays in protected TEMP folder + [string]$xmlSchTaskFilePath = "$dirAppDeployTemp\SchTaskUnBlockApps.xml" + [string]$xmlUnblockAppsSchTask | Out-File -FilePath $xmlSchTaskFilePath -Force -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to export the scheduled task XML file [$xmlSchTaskFilePath]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + Return + } + + ## Import the Scheduled Task XML file to create the Scheduled Task + [psobject]$schTaskResult = Execute-Process -Path $exeSchTasks -Parameters "/create /f /tn $schTaskBlockedAppsName /xml `"$xmlSchTaskFilePath`"" -WindowStyle 'Hidden' -CreateNoWindow -PassThru -ExitOnProcessFailure $false + If ($schTaskResult.ExitCode -ne 0) { + Write-Log -Message "Failed to create the scheduled task [$schTaskBlockedAppsName] by importing the scheduled task XML file [$xmlSchTaskFilePath]." -Severity 3 -Source ${CmdletName} + Return + } + } + + [string[]]$blockProcessName = $processName + ## Append .exe to match registry keys + [string[]]$blockProcessName = $blockProcessName | ForEach-Object { $_ + '.exe' } -ErrorAction 'SilentlyContinue' + + ## Enumerate each process and set the debugger value to block application execution + ForEach ($blockProcess in $blockProcessName) { + Write-Log -Message "Set the Image File Execution Option registry key to block execution of [$blockProcess]." -Source ${CmdletName} + Set-RegistryKey -Key (Join-Path -Path $regKeyAppExecution -ChildPath $blockProcess) -Name 'Debugger' -Value $debuggerBlockValue -ContinueOnError $true + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Unblock-AppExecution +Function Unblock-AppExecution { +<# +.SYNOPSIS + Unblocks the execution of applications performed by the Block-AppExecution function +.DESCRIPTION + This function is called by the Exit-Script function or when the script itself is called with the parameters -CleanupBlockedApps +.EXAMPLE + Unblock-AppExecution +.NOTES + This is an internal script function and should typically not be called directly. + It is used when the -BlockExecution parameter is specified with the Show-InstallationWelcome function to undo the actions performed by Block-AppExecution. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Bypass if no Admin rights + If ($configToolkitRequireAdmin -eq $false) { + Write-Log -Message "Bypassing Function [${CmdletName}], because [Require Admin: $configToolkitRequireAdmin]." -Source ${CmdletName} + Return + } + ## Bypass if in NonInteractive mode + If ($deployModeNonInteractive) { + Write-Log -Message "Bypassing Function [${CmdletName}], because [Mode: $deployMode]." -Source ${CmdletName} + Return + } + + ## Remove Debugger values to unblock processes + [psobject[]]$unblockProcesses = $null + [psobject[]]$unblockProcesses += (Get-ChildItem -LiteralPath $regKeyAppExecution -Recurse -ErrorAction 'SilentlyContinue' | ForEach-Object { Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction 'SilentlyContinue'}) + ForEach ($unblockProcess in ($unblockProcesses | Where-Object { $_.Debugger -like '*AppDeployToolkit_BlockAppExecutionMessage*' })) { + Write-Log -Message "Remove the Image File Execution Options registry key to unblock execution of [$($unblockProcess.PSChildName)]." -Source ${CmdletName} + $unblockProcess | Remove-ItemProperty -Name 'Debugger' -ErrorAction 'SilentlyContinue' + } + + ## If block execution variable is $true, set it to $false + If ($BlockExecution) { + # Make this variable globally available so we can check whether we need to call Unblock-AppExecution + Set-Variable -Name 'BlockExecution' -Value $false -Scope 'Script' + } + + ## Remove the scheduled task if it exists + [string]$schTaskBlockedAppsName = $installName + '_BlockedApps' + Try { + If (Get-SchedulerTask -ContinueOnError $true | Select-Object -Property 'TaskName' | Where-Object { $_.TaskName -eq "\$schTaskBlockedAppsName" }) { + Write-Log -Message "Delete Scheduled Task [$schTaskBlockedAppsName]." -Source ${CmdletName} + Execute-Process -Path $exeSchTasks -Parameters "/Delete /TN $schTaskBlockedAppsName /F" + } + } + Catch { + Write-Log -Message "Error retrieving/deleting Scheduled Task.`n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Remove BlockAppExecution Schedule Task XML file + [string]$xmlSchTaskFilePath = "$dirAppDeployTemp\SchTaskUnBlockApps.xml" + If (Test-Path -LiteralPath $xmlSchTaskFilePath) { + Remove-Item -Path $xmlSchTaskFilePath + } + + ## Remove BlockAppExection Temporary directory + [string]$blockExecutionTempPath = Join-Path -Path $dirAppDeployTemp -ChildPath 'BlockExecution' + If (Test-Path -LiteralPath $blockExecutionTempPath -PathType 'Container') { + Remove-Folder -Path $blockExecutionTempPath + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-DeferHistory +Function Get-DeferHistory { +<# +.SYNOPSIS + Get the history of deferrals from the registry for the current application, if it exists. +.DESCRIPTION + Get the history of deferrals from the registry for the current application, if it exists. +.EXAMPLE + Get-DeferHistory +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Write-Log -Message 'Get deferral history...' -Source ${CmdletName} + Get-RegistryKey -Key $regKeyDeferHistory -ContinueOnError $true + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-DeferHistory +Function Set-DeferHistory { +<# +.SYNOPSIS + Set the history of deferrals in the registry for the current application. +.DESCRIPTION + Set the history of deferrals in the registry for the current application. +.EXAMPLE + Set-DeferHistory +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [string]$deferTimesRemaining, + [Parameter(Mandatory=$false)] + [string]$deferDeadline + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If ($deferTimesRemaining -and ($deferTimesRemaining -ge 0)) { + Write-Log -Message "Set deferral history: [DeferTimesRemaining = $deferTimesRemaining]." -Source ${CmdletName} + Set-RegistryKey -Key $regKeyDeferHistory -Name 'DeferTimesRemaining' -Value $deferTimesRemaining -ContinueOnError $true + } + If ($deferDeadline) { + Write-Log -Message "Set deferral history: [DeferDeadline = $deferDeadline]." -Source ${CmdletName} + Set-RegistryKey -Key $regKeyDeferHistory -Name 'DeferDeadline' -Value $deferDeadline -ContinueOnError $true + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-UniversalDate +Function Get-UniversalDate { +<# +.SYNOPSIS + Returns the date/time for the local culture in a universal sortable date time pattern. +.DESCRIPTION + Converts the current datetime or a datetime string for the current culture into a universal sortable date time pattern, e.g. 2013-08-22 11:51:52Z +.PARAMETER DateTime + Specify the DateTime in the current culture. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default: $false. +.EXAMPLE + Get-UniversalDate + Returns the current date in a universal sortable date time pattern. +.EXAMPLE + Get-UniversalDate -DateTime '25/08/2013' + Returns the date for the current culture in a universal sortable date time pattern. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + # Get the current date + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$DateTime = ((Get-Date -Format ($culture).DateTimeFormat.UniversalDateTimePattern).ToString()), + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## If a universal sortable date time pattern was provided, remove the Z, otherwise it could get converted to a different time zone. + If ($DateTime -match 'Z$') { $DateTime = $DateTime -replace 'Z$', '' } + [datetime]$DateTime = [datetime]::Parse($DateTime, $culture) + + ## Convert the date to a universal sortable date time pattern based on the current culture + Write-Log -Message "Convert the date [$DateTime] to a universal sortable date time pattern based on the current culture [$($culture.Name)]." -Source ${CmdletName} + [string]$universalDateTime = (Get-Date -Date $DateTime -Format ($culture).DateTimeFormat.UniversalSortableDateTimePattern -ErrorAction 'Stop').ToString() + Write-Output -InputObject $universalDateTime + } + Catch { + Write-Log -Message "The specified date/time [$DateTime] is not in a format recognized by the current culture [$($culture.Name)]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "The specified date/time [$DateTime] is not in a format recognized by the current culture: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-RunningProcesses +Function Get-RunningProcesses { +<# +.SYNOPSIS + Gets the processes that are running from a custom list of process objects and also adds a property called ProcessDescription. +.DESCRIPTION + Gets the processes that are running from a custom list of process objects and also adds a property called ProcessDescription. +.PARAMETER ProcessObjects + Custom object containing the process objects to search for. If not supplied, the function just returns $null +.EXAMPLE + Get-RunningProcesses -ProcessObjects $ProcessObjects +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false,Position=0)] + [psobject[]]$ProcessObjects, + [Parameter(Mandatory=$false,Position=1)] + [switch]$DisableLogging + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If ($processObjects -and $processObjects[0].ProcessName) { + [string]$runningAppsCheck = $processObjects.ProcessName -join ',' + If (-not($DisableLogging)) { + Write-Log -Message "Check for running applications: [$runningAppsCheck]" -Source ${CmdletName} + } + ## Prepare a filter for Where-Object + [scriptblock]$whereObjectFilter = { + ForEach ($processObject in $processObjects) { + If ($_.ProcessName -ieq $processObject.ProcessName) { + If ($processObject.ProcessDescription) { + # The description of the process provided as a Parameter to the function, e.g. -ProcessName "winword=Microsoft Office Word". + Add-Member -InputObject $_ -MemberType 'NoteProperty' -Name 'ProcessDescription' -Value $processObject.ProcessDescription -Force -PassThru -ErrorAction 'SilentlyContinue' + } + ElseIf ($_.Description) { + # If the process already has a description field specified, then use it + Add-Member -InputObject $_ -MemberType 'NoteProperty' -Name 'ProcessDescription' -Value $_.Description -Force -PassThru -ErrorAction 'SilentlyContinue' + } + Else { + # Fall back on the process name if no description is provided by the process or as a parameter to the function + Add-Member -InputObject $_ -MemberType 'NoteProperty' -Name 'ProcessDescription' -Value $_.ProcessName -Force -PassThru -ErrorAction 'SilentlyContinue' + } + Write-Output $true + return; + } + } + + Write-Output $false + return; + } + ## Get all running processes and escape special characters. Match against the process names to search for to find running processes. + [Diagnostics.Process[]]$runningProcesses = Get-Process | Where-Object -FilterScript $whereObjectFilter | Sort-Object ProcessName + + If (-not($DisableLogging)) { + If ($runningProcesses) { + [string]$runningProcessList = ($runningProcesses.ProcessName | Select-Object -Unique) -join ',' + Write-Log -Message "The following processes are running: [$runningProcessList]." -Source ${CmdletName} + } + Else { + Write-Log -Message 'Specified applications are not running.' -Source ${CmdletName} + } + } + Write-Output -InputObject $runningProcesses + } Else { + Write-Output -InputObject $null + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-InstallationWelcome +Function Show-InstallationWelcome { +<# +.SYNOPSIS + Show a welcome dialog prompting the user with information about the installation and actions to be performed before the installation can begin. +.DESCRIPTION + The following prompts can be included in the welcome dialog: + a) Close the specified running applications, or optionally close the applications without showing a prompt (using the -Silent switch). + b) Defer the installation a certain number of times, for a certain number of days or until a deadline is reached. + c) Countdown until applications are automatically closed. + d) Prevent users from launching the specified applications while the installation is in progress. + Notes: + The process descriptions are retrieved from WMI, with a fall back on the process name if no description is available. Alternatively, you can specify the description yourself with a '=' symbol - see examples. + The dialog box will timeout after the timeout specified in the XML configuration file (default 1 hour and 55 minutes) to prevent SCCM installations from timing out and returning a failure code to SCCM. When the dialog times out, the script will exit and return a 1618 code (SCCM fast retry code). +.PARAMETER CloseApps + Name of the process to stop (do not include the .exe). Specify multiple processes separated by a comma. Specify custom descriptions like this: "winword=Microsoft Office Word,excel=Microsoft Office Excel" +.PARAMETER Silent + Stop processes without prompting the user. +.PARAMETER CloseAppsCountdown + Option to provide a countdown in seconds until the specified applications are automatically closed. This only takes effect if deferral is not allowed or has expired. +.PARAMETER ForceCloseAppsCountdown + Option to provide a countdown in seconds until the specified applications are automatically closed regardless of whether deferral is allowed. +.PARAMETER PromptToSave + Specify whether to prompt to save working documents when the user chooses to close applications by selecting the "Close Programs" button. Option does not work in SYSTEM context unless toolkit launched with "psexec.exe -s -i" to run it as an interactive process under the SYSTEM account. +.PARAMETER PersistPrompt + Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml. The user will have no option but to respond to the prompt. This only takes effect if deferral is not allowed or has expired. +.PARAMETER BlockExecution + Option to prevent the user from launching the process/application during the installation. +.PARAMETER AllowDefer + Enables an optional defer button to allow the user to defer the installation. +.PARAMETER AllowDeferCloseApps + Enables an optional defer button to allow the user to defer the installation only if there are running applications that need to be closed. +.PARAMETER DeferTimes + Specify the number of times the installation can be deferred. +.PARAMETER DeferDays + Specify the number of days since first run that the installation can be deferred. This is converted to a deadline. +.PARAMETER DeferDeadline + Specify the deadline date until which the installation can be deferred. + Specify the date in the local culture if the script is intended for that same culture. + If the script is intended to run on EN-US machines, specify the date in the format: "08/25/2013" or "08-25-2013" or "08-25-2013 18:00:00" + If the script is intended for multiple cultures, specify the date in the universal sortable date/time format: "2013-08-22 11:51:52Z" + The deadline date will be displayed to the user in the format of their culture. +.PARAMETER CheckDiskSpace + Specify whether to check if there is enough disk space for the installation to proceed. + If this parameter is specified without the RequiredDiskSpace parameter, the required disk space is calculated automatically based on the size of the script source and associated files. +.PARAMETER RequiredDiskSpace + Specify required disk space in MB, used in combination with CheckDiskSpace. +.PARAMETER MinimizeWindows + Specifies whether to minimize other windows when displaying prompt. Default: $true. +.PARAMETER TopMost + Specifies whether the windows is the topmost window. Default: $true. +.PARAMETER ForceCountdown + Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled. +.PARAMETER CustomText + Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'iexplore,winword,excel' + Prompt the user to close Internet Explorer, Word and Excel. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'winword,excel' -Silent + Close Word and Excel without prompting the user. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'winword,excel' -BlockExecution + Close Word and Excel and prevent the user from launching the applications while the installation is in progress. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'winword=Microsoft Office Word,excel=Microsoft Office Excel' -CloseAppsCountdown 600 + Prompt the user to close Word and Excel, with customized descriptions for the applications and automatically close the applications after 10 minutes. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'winword,msaccess,excel' -PersistPrompt + Prompt the user to close Word, MSAccess and Excel. + By using the PersistPrompt switch, the dialog will return to the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml, so the user cannot ignore it by dragging it aside. +.EXAMPLE + Show-InstallationWelcome -AllowDefer -DeferDeadline '25/08/2013' + Allow the user to defer the installation until the deadline is reached. +.EXAMPLE + Show-InstallationWelcome -CloseApps 'winword,excel' -BlockExecution -AllowDefer -DeferTimes 10 -DeferDeadline '25/08/2013' -CloseAppsCountdown 600 + Close Word and Excel and prevent the user from launching the applications while the installation is in progress. + Allow the user to defer the installation a maximum of 10 times or until the deadline is reached, whichever happens first. + When deferral expires, prompt the user to close the applications and automatically close them after 10 minutes. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding(DefaultParametersetName='None')] + + Param ( + ## Specify process names separated by commas. Optionally specify a process description with an equals symbol, e.g. "winword=Microsoft Office Word" + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$CloseApps, + ## Specify whether to prompt user or force close the applications + [Parameter(Mandatory=$false)] + [switch]$Silent = $false, + ## Specify a countdown to display before automatically closing applications where deferral is not allowed or has expired + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$CloseAppsCountdown = 0, + ## Specify a countdown to display before automatically closing applications whether or not deferral is allowed + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$ForceCloseAppsCountdown = 0, + ## Specify whether to prompt to save working documents when the user chooses to close applications by selecting the "Close Programs" button + [Parameter(Mandatory=$false)] + [switch]$PromptToSave = $false, + ## Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml. + [Parameter(Mandatory=$false)] + [switch]$PersistPrompt = $false, + ## Specify whether to block execution of the processes during installation + [Parameter(Mandatory=$false)] + [switch]$BlockExecution = $false, + ## Specify whether to enable the optional defer button on the dialog box + [Parameter(Mandatory=$false)] + [switch]$AllowDefer = $false, + ## Specify whether to enable the optional defer button on the dialog box only if an app needs to be closed + [Parameter(Mandatory=$false)] + [switch]$AllowDeferCloseApps = $false, + ## Specify the number of times the deferral is allowed + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$DeferTimes = 0, + ## Specify the number of days since first run that the deferral is allowed + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$DeferDays = 0, + ## Specify the deadline (in format dd/mm/yyyy) for which deferral will expire as an option + [Parameter(Mandatory=$false)] + [string]$DeferDeadline = '', + ## Specify whether to check if there is enough disk space for the installation to proceed. If this parameter is specified without the RequiredDiskSpace parameter, the required disk space is calculated automatically based on the size of the script source and associated files. + [Parameter(ParameterSetName = "CheckDiskSpaceParameterSet",Mandatory=$true)] + [ValidateScript({$_.IsPresent -eq ($true -or $false)})] + [switch]$CheckDiskSpace, + ## Specify required disk space in MB, used in combination with $CheckDiskSpace. + [Parameter(ParameterSetName = "CheckDiskSpaceParameterSet",Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$RequiredDiskSpace = 0, + ## Specify whether to minimize other windows when displaying prompt + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$MinimizeWindows = $true, + ## Specifies whether the window is the topmost window + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$TopMost = $true, + ## Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$ForceCountdown = 0, + ## Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML. + [Parameter(Mandatory=$false)] + [switch]$CustomText = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## If running in NonInteractive mode, force the processes to close silently + If ($deployModeNonInteractive) { $Silent = $true } + + ## If using Zero-Config MSI Deployment, append any executables found in the MSI to the CloseApps list + If ($useDefaultMsi) { $CloseApps = "$CloseApps,$defaultMsiExecutablesList" } + + ## Check disk space requirements if specified + If ($CheckDiskSpace) { + Write-Log -Message 'Evaluate disk space requirements.' -Source ${CmdletName} + [double]$freeDiskSpace = Get-FreeDiskSpace + If ($RequiredDiskSpace -eq 0) { + Try { + # Determine the size of the Files folder + $fso = New-Object -ComObject 'Scripting.FileSystemObject' -ErrorAction 'Stop' + $RequiredDiskSpace = [math]::Round((($fso.GetFolder($scriptParentPath).Size) / 1MB)) + } + Catch { + Write-Log -Message "Failed to calculate disk space requirement from source files. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + If ($freeDiskSpace -lt $RequiredDiskSpace) { + Write-Log -Message "Failed to meet minimum disk space requirement. Space Required [$RequiredDiskSpace MB], Space Available [$freeDiskSpace MB]." -Severity 3 -Source ${CmdletName} + If (-not $Silent) { + Show-InstallationPrompt -Message ($configDiskSpaceMessage -f $installTitle, $RequiredDiskSpace, ($freeDiskSpace)) -ButtonRightText 'OK' -Icon 'Error' + } + Exit-Script -ExitCode $configInstallationUIExitCode + } + Else { + Write-Log -Message 'Successfully passed minimum disk space requirement check.' -Source ${CmdletName} + } + } + + If ($CloseApps) { + ## Create a Process object with custom descriptions where they are provided (split on an '=' sign) + [psobject[]]$processObjects = @() + # Split multiple processes on a comma, then split on equal sign, then create custom object with process name and description + ForEach ($process in ($CloseApps -split ',' | Where-Object { $_ })) { + If ($process.Contains('=')) { + [string[]]$ProcessSplit = $process -split '=' + $processObjects += New-Object -TypeName 'PSObject' -Property @{ + ProcessName = $ProcessSplit[0] + ProcessDescription = $ProcessSplit[1] + } + } + Else { + [string]$ProcessInfo = $process + $processObjects += New-Object -TypeName 'PSObject' -Property @{ + ProcessName = $process + ProcessDescription = '' + } + } + } + } + + ## Check Deferral history and calculate remaining deferrals + If (($allowDefer) -or ($AllowDeferCloseApps)) { + # Set $allowDefer to true if $AllowDeferCloseApps is true + $allowDefer = $true + + # Get the deferral history from the registry + $deferHistory = Get-DeferHistory + $deferHistoryTimes = $deferHistory | Select-Object -ExpandProperty 'DeferTimesRemaining' -ErrorAction 'SilentlyContinue' + $deferHistoryDeadline = $deferHistory | Select-Object -ExpandProperty 'DeferDeadline' -ErrorAction 'SilentlyContinue' + + # Reset Switches + $checkDeferDays = $false + $checkDeferDeadline = $false + If ($DeferDays -ne 0) { $checkDeferDays = $true } + If ($DeferDeadline) { $checkDeferDeadline = $true } + If ($DeferTimes -ne 0) { + If ($deferHistoryTimes -ge 0) { + Write-Log -Message "Defer history shows [$($deferHistory.DeferTimesRemaining)] deferrals remaining." -Source ${CmdletName} + $DeferTimes = $deferHistory.DeferTimesRemaining - 1 + } + Else { + $DeferTimes = $DeferTimes - 1 + } + Write-Log -Message "User has [$deferTimes] deferrals remaining." -Source ${CmdletName} + If ($DeferTimes -lt 0) { + Write-Log -Message 'Deferral has expired.' -Source ${CmdletName} + $AllowDefer = $false + } + } + Else { + If (Test-Path -LiteralPath 'variable:deferTimes') { Remove-Variable -Name 'deferTimes' } + $DeferTimes = $null + } + If ($checkDeferDays -and $allowDefer) { + If ($deferHistoryDeadline) { + Write-Log -Message "Defer history shows a deadline date of [$deferHistoryDeadline]." -Source ${CmdletName} + [string]$deferDeadlineUniversal = Get-UniversalDate -DateTime $deferHistoryDeadline + } + Else { + [string]$deferDeadlineUniversal = Get-UniversalDate -DateTime (Get-Date -Date ((Get-Date).AddDays($deferDays)) -Format ($culture).DateTimeFormat.UniversalDateTimePattern).ToString() + } + Write-Log -Message "User has until [$deferDeadlineUniversal] before deferral expires." -Source ${CmdletName} + If ((Get-UniversalDate) -gt $deferDeadlineUniversal) { + Write-Log -Message 'Deferral has expired.' -Source ${CmdletName} + $AllowDefer = $false + } + } + If ($checkDeferDeadline -and $allowDefer) { + # Validate Date + Try { + [string]$deferDeadlineUniversal = Get-UniversalDate -DateTime $deferDeadline -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Date is not in the correct format for the current culture. Type the date in the current locale format, such as 20/08/2014 (Europe) or 08/20/2014 (United States). If the script is intended for multiple cultures, specify the date in the universal sortable date/time format, e.g. '2013-08-22 11:51:52Z'. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + Throw "Date is not in the correct format for the current culture. Type the date in the current locale format, such as 20/08/2014 (Europe) or 08/20/2014 (United States). If the script is intended for multiple cultures, specify the date in the universal sortable date/time format, e.g. '2013-08-22 11:51:52Z': $($_.Exception.Message)" + } + Write-Log -Message "User has until [$deferDeadlineUniversal] remaining." -Source ${CmdletName} + If ((Get-UniversalDate) -gt $deferDeadlineUniversal) { + Write-Log -Message 'Deferral has expired.' -Source ${CmdletName} + $AllowDefer = $false + } + } + } + If (($deferTimes -lt 0) -and (-not ($deferDeadlineUniversal))) { $AllowDefer = $false } + + ## Prompt the user to close running applications and optionally defer if enabled + If (-not ($deployModeSilent) -and (-not ($silent))) { + If ($forceCloseAppsCountdown -gt 0) { + # Keep the same variable for countdown to simplify the code: + $closeAppsCountdown = $forceCloseAppsCountdown + # Change this variable to a boolean now to switch the countdown on even with deferral + [boolean]$forceCloseAppsCountdown = $true + } + ElseIf ($forceCountdown -gt 0){ + # Keep the same variable for countdown to simplify the code: + $closeAppsCountdown = $forceCountdown + # Change this variable to a boolean now to switch the countdown on + [boolean]$forceCountdown = $true + } + Set-Variable -Name 'closeAppsCountdownGlobal' -Value $closeAppsCountdown -Scope 'Script' + + While ((Get-RunningProcesses -ProcessObjects $processObjects -OutVariable 'runningProcesses') -or (($promptResult -ne 'Defer') -and ($promptResult -ne 'Close'))) { + [string]$runningProcessDescriptions = ($runningProcesses | Where-Object { $_.ProcessDescription } | Select-Object -ExpandProperty 'ProcessDescription' | Select-Object -Unique | Sort-Object) -join ',' + # Check if we need to prompt the user to defer, to defer and close apps, or not to prompt them at all + If ($allowDefer) { + # If there is deferral and closing apps is allowed but there are no apps to be closed, break the while loop + If ($AllowDeferCloseApps -and (-not $runningProcessDescriptions)) { + Break + } + # Otherwise, as long as the user has not selected to close the apps or the processes are still running and the user has not selected to continue, prompt user to close running processes with deferral + ElseIf (($promptResult -ne 'Close') -or (($runningProcessDescriptions) -and ($promptResult -ne 'Continue'))) { + [string]$promptResult = Show-WelcomePrompt -ProcessDescriptions $runningProcessDescriptions -CloseAppsCountdown $closeAppsCountdownGlobal -ForceCloseAppsCountdown $forceCloseAppsCountdown -ForceCountdown $forceCountdown -PersistPrompt $PersistPrompt -AllowDefer -DeferTimes $deferTimes -DeferDeadline $deferDeadlineUniversal -MinimizeWindows $MinimizeWindows -CustomText:$CustomText -TopMost $TopMost + } + } + # If there is no deferral and processes are running, prompt the user to close running processes with no deferral option + ElseIf (($runningProcessDescriptions) -or ($forceCountdown)) { + [string]$promptResult = Show-WelcomePrompt -ProcessDescriptions $runningProcessDescriptions -CloseAppsCountdown $closeAppsCountdownGlobal -ForceCloseAppsCountdown $forceCloseAppsCountdown -ForceCountdown $forceCountdown -PersistPrompt $PersistPrompt -MinimizeWindows $minimizeWindows -CustomText:$CustomText -TopMost $TopMost + } + # If there is no deferral and no processes running, break the while loop + Else { + Break + } + + # If the user has clicked OK, wait a few seconds for the process to terminate before evaluating the running processes again + If ($promptResult -eq 'Continue') { + Write-Log -Message 'User selected to continue...' -Source ${CmdletName} + Start-Sleep -Seconds 2 + + # Break the while loop if there are no processes to close and the user has clicked OK to continue + If (-not $runningProcesses) { Break } + } + # Force the applications to close + ElseIf ($promptResult -eq 'Close') { + Write-Log -Message 'User selected to force the application(s) to close...' -Source ${CmdletName} + If (($PromptToSave) -and ($SessionZero -and (-not $IsProcessUserInteractive))) { + Write-Log -Message 'Specified [-PromptToSave] option will not be available because current process is running in session zero and is not interactive.' -Severity 2 -Source ${CmdletName} + } + + ForEach ($runningProcess in $runningProcesses) { + [psobject[]]$AllOpenWindowsForRunningProcess = Get-WindowTitle -GetAllWindowTitles -DisableFunctionLogging | Where-Object { $_.ParentProcess -eq $runningProcess.Name } + # If the PromptToSave parameter was specified and the process has a window open, then prompt the user to save work if there is work to be saved when closing window + If (($PromptToSave) -and (-not ($SessionZero -and (-not $IsProcessUserInteractive))) -and ($AllOpenWindowsForRunningProcess) -and ($runningProcess.MainWindowHandle -ne [IntPtr]::Zero)) { + [timespan]$PromptToSaveTimeout = New-TimeSpan -Seconds $configInstallationPromptToSave + [Diagnostics.StopWatch]$PromptToSaveStopWatch = [Diagnostics.StopWatch]::StartNew() + $PromptToSaveStopWatch.Reset() + ForEach ($OpenWindow in $AllOpenWindowsForRunningProcess) { + Try { + Write-Log -Message "Stop process [$($runningProcess.Name)] with window title [$($OpenWindow.WindowTitle)] and prompt to save if there is work to be saved (timeout in [$configInstallationPromptToSave] seconds)..." -Source ${CmdletName} + [boolean]$IsBringWindowToFrontSuccess = [PSADT.UiAutomation]::BringWindowToFront($OpenWindow.WindowHandle) + [boolean]$IsCloseWindowCallSuccess = $runningProcess.CloseMainWindow() + If (-not $IsCloseWindowCallSuccess) { + Write-Log -Message "Failed to call the CloseMainWindow() method on process [$($runningProcess.Name)] with window title [$($OpenWindow.WindowTitle)] because the main window may be disabled due to a modal dialog being shown." -Severity 3 -Source ${CmdletName} + } + Else { + $PromptToSaveStopWatch.Start() + Do { + [boolean]$IsWindowOpen = [boolean](Get-WindowTitle -GetAllWindowTitles -DisableFunctionLogging | Where-Object { $_.WindowHandle -eq $OpenWindow.WindowHandle }) + If (-not $IsWindowOpen) { Break } + Start-Sleep -Seconds 3 + } While (($IsWindowOpen) -and ($PromptToSaveStopWatch.Elapsed -lt $PromptToSaveTimeout)) + $PromptToSaveStopWatch.Reset() + If ($IsWindowOpen) { + Write-Log -Message "Exceeded the [$configInstallationPromptToSave] seconds timeout value for the user to save work associated with process [$($runningProcess.Name)] with window title [$($OpenWindow.WindowTitle)]." -Severity 2 -Source ${CmdletName} + } + Else { + Write-Log -Message "Window [$($OpenWindow.WindowTitle)] for process [$($runningProcess.Name)] was successfully closed." -Source ${CmdletName} + } + } + } + Catch { + Write-Log -Message "Failed to close window [$($OpenWindow.WindowTitle)] for process [$($runningProcess.Name)]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + Continue + } + Finally { + $runningProcess.Refresh() + } + } + } + Else { + Write-Log -Message "Stop process $($runningProcess.Name)..." -Source ${CmdletName} + Stop-Process -Id $runningProcess.Id -Force -ErrorAction 'SilentlyContinue' + } + } + Start-Sleep -Seconds 2 + } + # Stop the script (if not actioned before the timeout value) + ElseIf ($promptResult -eq 'Timeout') { + Write-Log -Message 'Installation not actioned before the timeout value.' -Source ${CmdletName} + $BlockExecution = $false + + If (($deferTimes -ge 0) -or ($deferDeadlineUniversal)) { + Set-DeferHistory -DeferTimesRemaining $DeferTimes -DeferDeadline $deferDeadlineUniversal + } + ## Dispose the welcome prompt timer here because if we dispose it within the Show-WelcomePrompt function we risk resetting the timer and missing the specified timeout period + If ($script:welcomeTimer) { + Try { + $script:welcomeTimer.Dispose() + $script:welcomeTimer = $null + } + Catch { } + } + + # Restore minimized windows + $null = $shellApp.UndoMinimizeAll() + + Exit-Script -ExitCode $configInstallationUIExitCode + } + # Stop the script (user chose to defer) + ElseIf ($promptResult -eq 'Defer') { + Write-Log -Message 'Installation deferred by the user.' -Source ${CmdletName} + $BlockExecution = $false + + Set-DeferHistory -DeferTimesRemaining $DeferTimes -DeferDeadline $deferDeadlineUniversal + + # Restore minimized windows + $null = $shellApp.UndoMinimizeAll() + + Exit-Script -ExitCode $configInstallationDeferExitCode + } + } + } + + ## Force the processes to close silently, without prompting the user + If (($Silent -or $deployModeSilent) -and $CloseApps) { + [array]$runningProcesses = $null + [array]$runningProcesses = Get-RunningProcesses $processObjects + If ($runningProcesses) { + [string]$runningProcessDescriptions = ($runningProcesses | Where-Object { $_.ProcessDescription } | Select-Object -ExpandProperty 'ProcessDescription' | Select-Object -Unique | Sort-Object) -join ',' + Write-Log -Message "Force close application(s) [$($runningProcessDescriptions)] without prompting user." -Source ${CmdletName} + $runningProcesses | Stop-Process -Force -ErrorAction 'SilentlyContinue' + Start-Sleep -Seconds 2 + } + } + + ## Force nsd.exe to stop if Notes is one of the required applications to close + If (($processObjects | Select-Object -ExpandProperty 'ProcessName') -contains 'notes') { + ## Get the path where Notes is installed + [string]$notesPath = Get-Item -LiteralPath $regKeyLotusNotes -ErrorAction 'SilentlyContinue' | Get-ItemProperty | Select-Object -ExpandProperty 'Path' + + ## Ensure we aren't running as a Local System Account and Notes install directory was found + If ((-not $IsLocalSystemAccount) -and ($notesPath)) { + # Get a list of all the executables in the Notes folder + [string[]]$notesPathExes = Get-ChildItem -LiteralPath $notesPath -Filter '*.exe' -Recurse | Select-Object -ExpandProperty 'BaseName' | Sort-Object + ## Check for running Notes executables and run NSD if any are found + $notesPathExes | ForEach-Object { + If ((Get-Process | Select-Object -ExpandProperty 'Name') -contains $_) { + [string]$notesNSDExecutable = Join-Path -Path $notesPath -ChildPath 'NSD.exe' + Try { + If (Test-Path -LiteralPath $notesNSDExecutable -PathType 'Leaf' -ErrorAction 'Stop') { + Write-Log -Message "Execute [$notesNSDExecutable] with the -kill argument..." -Source ${CmdletName} + [Diagnostics.Process]$notesNSDProcess = Start-Process -FilePath $notesNSDExecutable -ArgumentList '-kill' -WindowStyle 'Hidden' -PassThru -ErrorAction 'SilentlyContinue' + + If (-not ($notesNSDProcess.WaitForExit(10000))) { + Write-Log -Message "[$notesNSDExecutable] did not end in a timely manner. Force terminate process." -Source ${CmdletName} + Stop-Process -Name 'NSD' -Force -ErrorAction 'SilentlyContinue' + } + } + } + Catch { + Write-Log -Message "Failed to launch [$notesNSDExecutable]. `n$(Resolve-Error)" -Source ${CmdletName} + } + + Write-Log -Message "[$notesNSDExecutable] returned exit code [$($notesNSDProcess.ExitCode)]." -Source ${CmdletName} + + # Force NSD process to stop in case the previous command was not successful + Stop-Process -Name 'NSD' -Force -ErrorAction 'SilentlyContinue' + } + } + } + + # Strip all Notes processes from the process list except notes.exe, because the other notes processes (e.g. notes2.exe) may be invoked by the Notes installation, so we don't want to block their execution. + If ($notesPathExes) { + [array]$processesIgnoringNotesExceptions = Compare-Object -ReferenceObject ($processObjects | Select-Object -ExpandProperty 'ProcessName' | Sort-Object) -DifferenceObject $notesPathExes -IncludeEqual | Where-Object { ($_.SideIndicator -eq '<=') -or ($_.InputObject -eq 'notes') } | Select-Object -ExpandProperty 'InputObject' + [array]$processObjects = $processObjects | Where-Object { $processesIgnoringNotesExceptions -contains $_.ProcessName } + } + } + + ## If block execution switch is true, call the function to block execution of these processes + If ($BlockExecution) { + # Make this variable globally available so we can check whether we need to call Unblock-AppExecution + Set-Variable -Name 'BlockExecution' -Value $BlockExecution -Scope 'Script' + Write-Log -Message '[-BlockExecution] parameter specified.' -Source ${CmdletName} + Block-AppExecution -ProcessName ($processObjects | Select-Object -ExpandProperty 'ProcessName') + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-WelcomePrompt +Function Show-WelcomePrompt { +<# +.SYNOPSIS + Called by Show-InstallationWelcome to prompt the user to optionally do the following: + 1) Close the specified running applications. + 2) Provide an option to defer the installation. + 3) Show a countdown before applications are automatically closed. +.DESCRIPTION + The user is presented with a Windows Forms dialog box to close the applications themselves and continue or to have the script close the applications for them. + If the -AllowDefer option is set to true, an optional "Defer" button will be shown to the user. If they select this option, the script will exit and return a 1618 code (SCCM fast retry code). + The dialog box will timeout after the timeout specified in the XML configuration file (default 1 hour and 55 minutes) to prevent SCCM installations from timing out and returning a failure code to SCCM. When the dialog times out, the script will exit and return a 1618 code (SCCM fast retry code). +.PARAMETER ProcessDescriptions + The descriptive names of the applications that are running and need to be closed. +.PARAMETER CloseAppsCountdown + Specify the countdown time in seconds before running applications are automatically closed when deferral is not allowed or expired. +.PARAMETER ForceCloseAppsCountdown + Specify whether to show the countdown regardless of whether deferral is allowed. +.PARAMETER PersistPrompt + Specify whether to make the prompt persist in the center of the screen every couple of seconds, specified in the AppDeployToolkitConfig.xml. +.PARAMETER AllowDefer + Specify whether to provide an option to defer the installation. +.PARAMETER DeferTimes + Specify the number of times the user is allowed to defer. +.PARAMETER DeferDeadline + Specify the deadline date before the user is allowed to defer. +.PARAMETER MinimizeWindows + Specifies whether to minimize other windows when displaying prompt. Default: $true. +.PARAMETER TopMost + Specifies whether the windows is the topmost window. Default: $true. +.PARAMETER ForceCountdown + Specify a countdown to display before automatically proceeding with the installation when a deferral is enabled. +.PARAMETER CustomText + Specify whether to display a custom message specified in the XML file. Custom message must be populated for each language section in the XML. +.EXAMPLE + Show-WelcomePrompt -ProcessDescriptions 'Lotus Notes, Microsoft Word' -CloseAppsCountdown 600 -AllowDefer -DeferTimes 10 +.NOTES + This is an internal script function and should typically not be called directly. It is used by the Show-InstallationWelcome prompt to display a custom prompt. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [string]$ProcessDescriptions, + [Parameter(Mandatory=$false)] + [int32]$CloseAppsCountdown, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ForceCloseAppsCountdown, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$PersistPrompt = $false, + [Parameter(Mandatory=$false)] + [switch]$AllowDefer = $false, + [Parameter(Mandatory=$false)] + [string]$DeferTimes, + [Parameter(Mandatory=$false)] + [string]$DeferDeadline, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$MinimizeWindows = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$TopMost = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$ForceCountdown = 0, + [Parameter(Mandatory=$false)] + [switch]$CustomText = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Reset switches + [boolean]$showCloseApps = $false + [boolean]$showDefer = $false + [boolean]$persistWindow = $false + + ## Reset times + [datetime]$startTime = Get-Date + [datetime]$countdownTime = $startTime + + ## Check if the countdown was specified + If ($CloseAppsCountdown) { + If ($CloseAppsCountdown -gt $configInstallationUITimeout) { + Throw 'The close applications countdown time cannot be longer than the timeout specified in the XML configuration for installation UI dialogs to timeout.' + } + } + + ## Initial form layout: Close Applications / Allow Deferral + If ($processDescriptions) { + Write-Log -Message "Prompt user to close application(s) [$processDescriptions]..." -Source ${CmdletName} + $showCloseApps = $true + } + If (($allowDefer) -and (($deferTimes -ge 0) -or ($deferDeadline))) { + Write-Log -Message 'User has the option to defer.' -Source ${CmdletName} + $showDefer = $true + If ($deferDeadline) { + # Remove the Z from universal sortable date time format, otherwise it could be converted to a different time zone + $deferDeadline = $deferDeadline -replace 'Z','' + # Convert the deadline date to a string + [string]$deferDeadline = (Get-Date -Date $deferDeadline).ToString() + } + } + + ## If deferral is being shown and 'close apps countdown' or 'persist prompt' was specified, enable those features. + If (-not $showDefer) { + If ($closeAppsCountdown -gt 0) { + Write-Log -Message "Close applications countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName} + $showCountdown = $true + } + } + If ($showDefer) { + If ($persistPrompt) { $persistWindow = $true } + } + ## If 'force close apps countdown' was specified, enable that feature. + If ($forceCloseAppsCountdown -eq $true) { + Write-Log -Message "Close applications countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName} + $showCountdown = $true + } + ## If 'force countdown' was specified, enable that feature. + If ($forceCountdown -eq $true) { + Write-Log -Message "Countdown has [$closeAppsCountdown] seconds remaining." -Source ${CmdletName} + $showCountdown = $true + } + + [string[]]$processDescriptions = $processDescriptions -split ',' + [Windows.Forms.Application]::EnableVisualStyles() + + $formWelcome = New-Object -TypeName 'System.Windows.Forms.Form' + $pictureBanner = New-Object -TypeName 'System.Windows.Forms.PictureBox' + $labelAppName = New-Object -TypeName 'System.Windows.Forms.Label' + $labelCountdown = New-Object -TypeName 'System.Windows.Forms.Label' + $labelDefer = New-Object -TypeName 'System.Windows.Forms.Label' + $listBoxCloseApps = New-Object -TypeName 'System.Windows.Forms.ListBox' + $buttonContinue = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonDefer = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonCloseApps = New-Object -TypeName 'System.Windows.Forms.Button' + $buttonAbort = New-Object -TypeName 'System.Windows.Forms.Button' + $formWelcomeWindowState = New-Object -TypeName 'System.Windows.Forms.FormWindowState' + $flowLayoutPanel = New-Object -TypeName 'System.Windows.Forms.FlowLayoutPanel' + $panelButtons = New-Object -TypeName 'System.Windows.Forms.Panel' + $toolTip = New-Object -TypeName 'System.Windows.Forms.ToolTip' + + ## Remove all event handlers from the controls + [scriptblock]$Form_Cleanup_FormClosed = { + Try { + $labelAppName.remove_Click($handler_labelAppName_Click) + $labelDefer.remove_Click($handler_labelDefer_Click) + $buttonCloseApps.remove_Click($buttonCloseApps_OnClick) + $buttonContinue.remove_Click($buttonContinue_OnClick) + $buttonDefer.remove_Click($buttonDefer_OnClick) + $buttonAbort.remove_Click($buttonAbort_OnClick) + $script:welcomeTimer.remove_Tick($timer_Tick) + $timerPersist.remove_Tick($timerPersist_Tick) + $timerRunningProcesses.remove_Tick($timerRunningProcesses_Tick) + $formWelcome.remove_Load($Form_StateCorrection_Load) + $formWelcome.remove_FormClosed($Form_Cleanup_FormClosed) + } + Catch { } + } + + [scriptblock]$Form_StateCorrection_Load = { + ## Correct the initial state of the form to prevent the .NET maximized form issue + $formWelcome.WindowState = 'Normal' + $formWelcome.AutoSize = $true + $formWelcome.TopMost = $TopMost + $formWelcome.BringToFront() + # Get the start position of the form so we can return the form to this position if PersistPrompt is enabled + Set-Variable -Name 'formWelcomeStartPosition' -Value $formWelcome.Location -Scope 'Script' + + ## Initialize the countdown timer + [datetime]$currentTime = Get-Date + [datetime]$countdownTime = $startTime.AddSeconds($CloseAppsCountdown) + $script:welcomeTimer.Start() + + ## Set up the form + [timespan]$remainingTime = $countdownTime.Subtract($currentTime) + [string]$labelCountdownSeconds = [string]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds) + If ($forceCountdown -eq $true) { + switch ($deploymentType){ + 'Install' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $($configDeploymentTypeInstall.ToLower())) + "`n$labelCountdownSeconds" } + 'Uninstall' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $($configDeploymentTypeUninstall.ToLower())) + "`n$labelCountdownSeconds" } + 'Repair' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $($configDeploymentTypeRepair.ToLower())) + "`n$labelCountdownSeconds" } + Default { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $($configDeploymentTypeInstall.ToLower())) + "`n$labelCountdownSeconds" } + } + } + Else { $labelCountdown.Text = "$configClosePromptCountdownMessage`n$labelCountdownSeconds" } + } + + ## Add the timer if it doesn't already exist - this avoids the timer being reset if the continue button is clicked + If (-not ($script:welcomeTimer)) { + $script:welcomeTimer = New-Object -TypeName 'System.Windows.Forms.Timer' + } + + If ($showCountdown) { + [scriptblock]$timer_Tick = { + ## Get the time information + [datetime]$currentTime = Get-Date + [datetime]$countdownTime = $startTime.AddSeconds($CloseAppsCountdown) + [timespan]$remainingTime = $countdownTime.Subtract($currentTime) + Set-Variable -Name 'closeAppsCountdownGlobal' -Value $remainingTime.TotalSeconds -Scope 'Script' + + ## If the countdown is complete, close the application(s) or continue + If ($countdownTime -le $currentTime) { + If ($forceCountdown -eq $true) { + Write-Log -Message 'Countdown timer has elapsed. Force continue.' -Source ${CmdletName} + $buttonContinue.PerformClick() + } + Else { + Write-Log -Message 'Close application(s) countdown timer has elapsed. Force closing application(s).' -Source ${CmdletName} + If ($buttonCloseApps.CanFocus) { $buttonCloseApps.PerformClick() } + Else { $buttonContinue.PerformClick() } + } + } + Else { + # Update the form + [string]$labelCountdownSeconds = [string]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds) + If ($forceCountdown -eq $true) { + switch ($deploymentType){ + 'Install' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeInstall) + "`n$labelCountdownSeconds" } + 'Uninstall' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeUninstall) + "`n$labelCountdownSeconds" } + 'Repair' { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeRepair) + "`n$labelCountdownSeconds" } + Default { $labelCountdown.Text = ($configWelcomePromptCountdownMessage -f $configDeploymentTypeInstall) + "`n$labelCountdownSeconds" } + } + } + Else { $labelCountdown.Text = "$configClosePromptCountdownMessage`n$labelCountdownSeconds" } + [Windows.Forms.Application]::DoEvents() + } + } + } + Else { + $script:welcomeTimer.Interval = ($configInstallationUITimeout * 1000) + [scriptblock]$timer_Tick = { $buttonAbort.PerformClick() } + } + + $script:welcomeTimer.add_Tick($timer_Tick) + + ## Persistence Timer + If ($persistWindow) { + $timerPersist = New-Object -TypeName 'System.Windows.Forms.Timer' + $timerPersist.Interval = ($configInstallationPersistInterval * 1000) + [scriptblock]$timerPersist_Tick = { Update-InstallationWelcome } + $timerPersist.add_Tick($timerPersist_Tick) + $timerPersist.Start() + } + + ## Process Re-Enumeration Timer + If ($configInstallationWelcomePromptDynamicRunningProcessEvaluation) { + $timerRunningProcesses = New-Object -TypeName 'System.Windows.Forms.Timer' + $timerRunningProcesses.Interval = ($configInstallationWelcomePromptDynamicRunningProcessEvaluationInterval * 1000) + [scriptblock]$timerRunningProcesses_Tick = { try { Get-RunningProcessesDynamically } catch {} } + $timerRunningProcesses.add_Tick($timerRunningProcesses_Tick) + $timerRunningProcesses.Start() + } + + ## Form + $formWelcome.Controls.Add($pictureBanner) + $formWelcome.Controls.Add($buttonAbort) + + ##---------------------------------------------- + ## Create zero px padding object + $paddingNone = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,0,0,0 + ## Create basic control size + $defaultControlSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,0 + + ## Generic Button properties + $buttonSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 110,24 + + ## Picture Banner + $pictureBanner.DataBindings.DefaultDataSourceUpdateMode = 0 + $pictureBanner.ImageLocation = $appDeployLogoBanner + $System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,0 + $pictureBanner.Location = $System_Drawing_Point + $pictureBanner.Name = 'pictureBanner' + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,$appDeployLogoBannerHeight + $pictureBanner.Size = $System_Drawing_Size + $pictureBanner.SizeMode = 'CenterImage' + $pictureBanner.Margin = $paddingNone + $pictureBanner.TabIndex = 0 + $pictureBanner.TabStop = $false + + ## Label App Name + $labelAppName.DataBindings.DefaultDataSourceUpdateMode = 0 + $labelAppName.Name = 'labelAppName' + $labelAppName.Size = $defaultControlSize + $labelAppName.MinimumSize = $defaultControlSize + $labelAppName.MaximumSize = $defaultControlSize + $labelAppName.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,5,0,5 + $labelAppName.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 10,0,10,0 + $labelAppName.TabIndex = 1 + + ## Initial form layout: Close Applications / Allow Deferral + $labelAppNameText = "$configDeferPromptWelcomeMessage`n`n$installTitle" + If ($showCloseApps) { + $labelAppNameText = "$labelAppNameText`n`n$configClosePromptMessage" + } + If ($CustomText -and $configWelcomePromptCustomMessage) { + $labelAppNameText = "$labelAppNameText`n`n$configWelcomePromptCustomMessage" + } + $labelAppName.Text = $labelAppNameText + $labelAppName.TextAlign = 'MiddleCenter' + $labelAppName.Anchor = 'Top' + $labelAppName.AutoSize = $true + $labelAppName.add_Click($handler_labelAppName_Click) + + ## Listbox Close Applications + $listBoxCloseApps.DataBindings.DefaultDataSourceUpdateMode = 0 + $listBoxCloseApps.FormattingEnabled = $true + $listBoxCloseApps.HorizontalScrollbar = $true + $listBoxCloseApps.Name = 'listBoxCloseApps' + $System_Drawing_Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 400,100 + $listBoxCloseApps.Size = $System_Drawing_Size + $listBoxCloseApps.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 25,0,0,0 + $listBoxCloseApps.TabIndex = 3 + $ProcessDescriptions | ForEach-Object { $null = $listboxCloseApps.Items.Add($_) } + + ## Label Defer + $labelDefer.DataBindings.DefaultDataSourceUpdateMode = 0 + $labelDefer.Name = 'labelDefer' + $labelDefer.Size = $defaultControlSize + $labelDefer.MinimumSize = $defaultControlSize + $labelDefer.MaximumSize = $defaultControlSize + $labelDefer.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,0,0,5 + $labelDefer.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 10,0,10,0 + $labelDefer.TabIndex = 4 + $deferralText = "$configDeferPromptExpiryMessage`n" + + If ($deferTimes -ge 0) { + $deferralText = "$deferralText `n$configDeferPromptRemainingDeferrals $([int32]$deferTimes + 1)" + } + If ($deferDeadline) { + $deferralText = "$deferralText `n$configDeferPromptDeadline $deferDeadline" + } + If (($deferTimes -lt 0) -and (-not $DeferDeadline)) { + $deferralText = "$deferralText `n$configDeferPromptNoDeadline" + } + $deferralText = "$deferralText `n`n$configDeferPromptWarningMessage" + $labelDefer.Text = $deferralText + $labelDefer.TextAlign = 'MiddleCenter' + $labelDefer.AutoSize = $true + $labelDefer.add_Click($handler_labelDefer_Click) + + ## Label Countdown + $labelCountdown.DataBindings.DefaultDataSourceUpdateMode = 0 + $labelCountdown.Name = 'labelCountdown' + $labelCountdown.Size = $defaultControlSize + $labelCountdown.MinimumSize = $defaultControlSize + $labelCountdown.MaximumSize = $defaultControlSize + $labelCountdown.Margin = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 0,0,0,5 + $labelCountdown.Padding = New-Object -TypeName 'System.Windows.Forms.Padding' -ArgumentList 10,0,10,0 + $labelCountdown.TabIndex = 4 + $labelCountdown.Font = New-Object -TypeName "System.Drawing.Font" -ArgumentList $labelCountdown.Font,1 + $labelCountdown.Text = '00:00:00' + $labelCountdown.TextAlign = 'MiddleCenter' + $labelCountdown.AutoSize = $true + $labelCountdown.add_Click($handler_labelDefer_Click) + + ## Panel Flow Layout + $System_Drawing_Point = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 0,$appDeployLogoBannerHeight + $flowLayoutPanel.Location = $System_Drawing_Point + $flowLayoutPanel.Margin = $paddingNone + $flowLayoutPanel.AutoSize = $true + $flowLayoutPanel.Anchor = 'Top' + $flowLayoutPanel.FlowDirection = 'TopDown' + $flowLayoutPanel.WrapContents = $true + $flowLayoutPanel.Controls.Add($labelAppName) + If ($showCloseApps) { $flowLayoutPanel.Controls.Add($listBoxCloseApps) } + If ($showDefer) { $flowLayoutPanel.Controls.Add($labelDefer) } + If ($showCountdown) { $flowLayoutPanel.Controls.Add($labelCountdown) } + + ## Button Close For Me + $buttonCloseApps.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonCloseApps.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 15,5 + $buttonCloseApps.Name = 'buttonCloseApps' + $buttonCloseApps.Size = $buttonSize + $buttonCloseApps.TabIndex = 5 + $buttonCloseApps.Text = $configClosePromptButtonClose + $buttonCloseApps.DialogResult = 'Yes' + $buttonCloseApps.AutoSize = $true + $buttonCloseApps.UseVisualStyleBackColor = $true + $buttonCloseApps.add_Click($buttonCloseApps_OnClick) + + ## Button Defer + $buttonDefer.DataBindings.DefaultDataSourceUpdateMode = 0 + If (-not $showCloseApps) { + $buttonDefer.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 15,5 + } + Else { + $buttonDefer.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 170,5 + } + $buttonDefer.Name = 'buttonDefer' + $buttonDefer.Size = $buttonSize + $buttonDefer.TabIndex = 6 + $buttonDefer.Text = $configClosePromptButtonDefer + $buttonDefer.DialogResult = 'No' + $buttonDefer.AutoSize = $true + $buttonDefer.UseVisualStyleBackColor = $true + $buttonDefer.add_Click($buttonDefer_OnClick) + + ## Button Continue + $buttonContinue.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonContinue.Location = New-Object -TypeName 'System.Drawing.Point' -ArgumentList 325,5 + $buttonContinue.Name = 'buttonContinue' + $buttonContinue.Size = $buttonSize + $buttonContinue.TabIndex = 7 + $buttonContinue.Text = $configClosePromptButtonContinue + $buttonContinue.DialogResult = 'OK' + $buttonContinue.AutoSize = $true + $buttonContinue.UseVisualStyleBackColor = $true + $buttonContinue.add_Click($buttonContinue_OnClick) + If ($showCloseApps) { + # Add tooltip to Continue button + $toolTip.BackColor = [Drawing.Color]::LightGoldenrodYellow + $toolTip.IsBalloon = $false + $toolTip.InitialDelay = 100 + $toolTip.ReshowDelay = 100 + $toolTip.SetToolTip($buttonContinue, $configClosePromptButtonContinueTooltip) + } + + ## Button Abort (Hidden) + $buttonAbort.DataBindings.DefaultDataSourceUpdateMode = 0 + $buttonAbort.Name = 'buttonAbort' + $buttonAbort.Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 1,1 + $buttonAbort.TabStop = $false + $buttonAbort.DialogResult = 'Abort' + $buttonAbort.TabIndex = 5 + $buttonAbort.Visible = $false + $buttonAbort.UseVisualStyleBackColor = $true + $buttonAbort.add_Click($buttonAbort_OnClick) + + ## Form Welcome + $formWelcome.Size = $defaultControlSize + $formWelcome.MinimumSize = $defaultControlSize + $formWelcome.Padding = $paddingNone + $formWelcome.Margin = $paddingNone + $formWelcome.DataBindings.DefaultDataSourceUpdateMode = 0 + $formWelcome.Name = 'WelcomeForm' + $formWelcome.Text = $installTitle + $formWelcome.StartPosition = 'CenterScreen' + $formWelcome.FormBorderStyle = 'FixedDialog' + $formWelcome.MaximizeBox = $false + $formWelcome.MinimizeBox = $false + $formWelcome.TopMost = $TopMost + $formWelcome.TopLevel = $true + $formWelcome.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + $formWelcome.AutoSize = $true + $formWelcome.Controls.Add($pictureBanner) + + ## Panel Button + $panelButtons.MinimumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.Size = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.MaximumSize = New-Object -TypeName 'System.Drawing.Size' -ArgumentList 450,34 + $panelButtons.AutoSize = $true + $panelButtons.Padding = $paddingNone + $panelButtons.Margin = $paddingNone + If ($showCloseApps) { $panelButtons.Controls.Add($buttonCloseApps) } + If ($showDefer) { $panelButtons.Controls.Add($buttonDefer) } + $panelButtons.Controls.Add($buttonContinue) + + ## Add the Buttons Panel to the flowPanel + $flowLayoutPanel.Controls.Add($panelButtons) + ## Add FlowPanel to the form + $formWelcome.Controls.Add($flowLayoutPanel) + + ## Save the initial state of the form + $formWelcomeWindowState = $formWelcome.WindowState + # Init the OnLoad event to correct the initial state of the form + $formWelcome.add_Load($Form_StateCorrection_Load) + # Clean up the control events + $formWelcome.add_FormClosed($Form_Cleanup_FormClosed) + + Function Update-InstallationWelcome { + $formWelcome.BringToFront() + $formWelcome.Location = "$($formWelcomeStartPosition.X),$($formWelcomeStartPosition.Y)" + $formWelcome.Refresh() + } + + # Function invoked by a timer to periodically check running processes dynamically whilst showing the welcome prompt + Function Get-RunningProcessesDynamically { + $dynamicRunningProcesses = $null + Get-RunningProcesses -ProcessObjects $processObjects -DisableLogging -OutVariable 'dynamicRunningProcesses' + [string]$dynamicRunningProcessDescriptions = ($dynamicRunningProcesses | Where-Object { $_.ProcessDescription } | Select-Object -ExpandProperty 'ProcessDescription' | Select-Object -Unique | Sort-Object) -join ',' + If ($dynamicRunningProcessDescriptions -ne $script:runningProcessDescriptions) { + # Update the runningProcessDescriptions variable for the next time this function runs + Set-Variable -Name 'runningProcessDescriptions' -Value $dynamicRunningProcessDescriptions -Force -Scope 'Script' + If ($dynamicrunningProcesses) { + Write-Log -Message "The running processes have changed. Updating the apps to close: [$script:runningProcessDescriptions]..." -Source ${CmdletName} + } + # Update the list box with the processes to close + $listboxCloseApps.Items.Clear() + $script:runningProcessDescriptions -split "," | ForEach-Object { $null = $listboxCloseApps.Items.Add($_) } + } + # If CloseApps processes were running when the prompt was shown, and they are subsequently detected to be closed while the form is showing, then close the form. The deferral and CloseApps conditions will be re-evaluated. + If ($ProcessDescriptions) { + If (-not ($dynamicRunningProcesses)) { + Write-Log -Message 'Previously detected running processes are no longer running.' -Source ${CmdletName} + $formWelcome.Dispose() + } + } + # If CloseApps processes were not running when the prompt was shown, and they are subsequently detected to be running while the form is showing, then close the form for relaunch. The deferral and CloseApps conditions will be re-evaluated. + ElseIf (-not $ProcessDescriptions) { + If ($dynamicRunningProcesses) { + Write-Log -Message 'New running processes detected. Updating the form to prompt to close the running applications.' -Source ${CmdletName} + $formWelcome.Dispose() + } + } + } + + ## Minimize all other windows + If ($minimizeWindows) { $null = $shellApp.MinimizeAll() } + + ## Show the form + $result = $formWelcome.ShowDialog() + $formWelcome.Dispose() + + Switch ($result) { + OK { $result = 'Continue' } + No { $result = 'Defer' } + Yes { $result = 'Close' } + Abort { $result = 'Timeout' } + } + + If ($configInstallationWelcomePromptDynamicRunningProcessEvaluation){ + $timerRunningProcesses.Stop() + } + + Write-Output -InputObject $result + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-InstallationRestartPrompt +Function Show-InstallationRestartPrompt { +<# +.SYNOPSIS + Displays a restart prompt with a countdown to a forced restart. +.DESCRIPTION + Displays a restart prompt with a countdown to a forced restart. +.PARAMETER CountdownSeconds + Specifies the number of seconds to countdown before the system restart. Default: 60 +.PARAMETER CountdownNoHideSeconds + Specifies the number of seconds to display the restart prompt without allowing the window to be hidden. Default: 30 +.PARAMETER NoSilentRestart + Specifies whether the restart should be triggered when Deploy mode is silent or very silent. Default: $true +.PARAMETER NoCountdown + Specifies not to show a countdown, just the Restart Now and Restart Later buttons. + The UI will restore/reposition itself persistently based on the interval value specified in the config file. +.PARAMETER SilentCountdownSeconds + Specifies number of seconds to countdown for the restart when the toolkit is running in silent mode and NoSilentRestart is $false. Default: 5 +.EXAMPLE + Show-InstallationRestartPrompt -Countdownseconds 600 -CountdownNoHideSeconds 60 +.EXAMPLE + Show-InstallationRestartPrompt -NoCountdown +.EXAMPLE + Show-InstallationRestartPrompt -Countdownseconds 300 -NoSilentRestart $false -SilentCountdownSeconds 10 +.NOTES + Be mindful of the countdown you specify for the reboot as code directly after this function might NOT be able to execute - that includes logging. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$CountdownSeconds = 60, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$CountdownNoHideSeconds = 30, + [Parameter(Mandatory=$false)] + [bool]$NoSilentRestart = $true, + [Parameter(Mandatory=$false)] + [switch]$NoCountdown = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$SilentCountdownSeconds = 5 + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## If in non-interactive mode + If ($deployModeSilent) { + If ($NoSilentRestart -eq $false) { + Write-Log -Message "Triggering restart silently, because the deploy mode is set to [$deployMode] and [NoSilentRestart] is disabled. Timeout is set to [$SilentCountdownSeconds] seconds." -Source ${CmdletName} + Start-Process -FilePath "$PSHOME\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -Command `"& {Start-Sleep -Seconds $SilentCountdownSeconds;Restart-Computer -Force;}`"" -WindowStyle 'Hidden' -ErrorAction 'SilentlyContinue' + } + Else { + Write-Log -Message "Skipping restart, because the deploy mode is set to [$deployMode] and [NoSilentRestart] is enabled." -Source ${CmdletName} + } + Return + } + ## Get the parameters passed to the function for invoking the function asynchronously + [hashtable]$installRestartPromptParameters = $psBoundParameters + + ## Check if we are already displaying a restart prompt + If (Get-Process | Where-Object { $_.MainWindowTitle -match $configRestartPromptTitle }) { + Write-Log -Message "${CmdletName} was invoked, but an existing restart prompt was detected. Cancelling restart prompt." -Severity 2 -Source ${CmdletName} + Return + } + + [datetime]$startTime = Get-Date + [datetime]$countdownTime = $startTime + + [Windows.Forms.Application]::EnableVisualStyles() + $formRestart = New-Object -TypeName 'System.Windows.Forms.Form' + $labelCountdown = New-Object -TypeName 'System.Windows.Forms.Label' + $labelTimeRemaining = New-Object -TypeName 'System.Windows.Forms.Label' + $labelMessage = New-Object -TypeName 'System.Windows.Forms.Label' + $buttonRestartLater = New-Object -TypeName 'System.Windows.Forms.Button' + $picturebox = New-Object -TypeName 'System.Windows.Forms.PictureBox' + $buttonRestartNow = New-Object -TypeName 'System.Windows.Forms.Button' + $timerCountdown = New-Object -TypeName 'System.Windows.Forms.Timer' + $InitialFormWindowState = New-Object -TypeName 'System.Windows.Forms.FormWindowState' + + [scriptblock]$RestartComputer = { + Write-Log -Message 'Force restart the computer...' -Source ${CmdletName} + Restart-Computer -Force + } + + [scriptblock]$FormEvent_Load = { + ## Initialize the countdown timer + [datetime]$currentTime = Get-Date + [datetime]$countdownTime = $startTime.AddSeconds($countdownSeconds) + $timerCountdown.Start() + ## Set up the form + [timespan]$remainingTime = $countdownTime.Subtract($currentTime) + $labelCountdown.Text = [string]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds) + If ($remainingTime.TotalSeconds -le $countdownNoHideSeconds) { $buttonRestartLater.Enabled = $false } + $formRestart.WindowState = 'Normal' + $formRestart.TopMost = $true + $formRestart.BringToFront() + } + + [scriptblock]$Form_StateCorrection_Load = { + ## Correct the initial state of the form to prevent the .NET maximized form issue + $formRestart.WindowState = $InitialFormWindowState + $formRestart.AutoSize = $true + $formRestart.TopMost = $true + $formRestart.BringToFront() + ## Get the start position of the form so we can return the form to this position if PersistPrompt is enabled + Set-Variable -Name 'formInstallationRestartPromptStartPosition' -Value $formRestart.Location -Scope 'Script' + } + + ## Persistence Timer + If ($NoCountdown) { + $timerPersist = New-Object -TypeName 'System.Windows.Forms.Timer' + $timerPersist.Interval = ($configInstallationRestartPersistInterval * 1000) + [scriptblock]$timerPersist_Tick = { + # Show the Restart Popup + $formRestart.WindowState = 'Normal' + $formRestart.TopMost = $true + $formRestart.BringToFront() + $formRestart.Location = "$($formInstallationRestartPromptStartPosition.X),$($formInstallationRestartPromptStartPosition.Y)" + $formRestart.Refresh() + [Windows.Forms.Application]::DoEvents() + } + $timerPersist.add_Tick($timerPersist_Tick) + $timerPersist.Start() + } + + [scriptblock]$buttonRestartLater_Click = { + ## Minimize the form + $formRestart.WindowState = 'Minimized' + If ($NoCountdown) { + ## Reset the persistence timer + $timerPersist.Stop() + $timerPersist.Start() + } + } + + ## Restart the computer + [scriptblock]$buttonRestartNow_Click = { & $RestartComputer } + + ## Hide the form if minimized + [scriptblock]$formRestart_Resize = { If ($formRestart.WindowState -eq 'Minimized') { $formRestart.WindowState = 'Minimized' } } + + [scriptblock]$timerCountdown_Tick = { + ## Get the time information + [datetime]$currentTime = Get-Date + [datetime]$countdownTime = $startTime.AddSeconds($countdownSeconds) + [timespan]$remainingTime = $countdownTime.Subtract($currentTime) + ## If the countdown is complete, restart the machine + If ($countdownTime -lt $currentTime) { + $buttonRestartNow.PerformClick() + } + Else { + ## Update the form + $labelCountdown.Text = [string]::Format('{0}:{1:d2}:{2:d2}', $remainingTime.Days * 24 + $remainingTime.Hours, $remainingTime.Minutes, $remainingTime.Seconds) + If ($remainingTime.TotalSeconds -le $countdownNoHideSeconds) { + $buttonRestartLater.Enabled = $false + # If the form is hidden when we hit the "No Hide", bring it back up + If ($formRestart.WindowState -eq 'Minimized') { + # Show Popup + $formRestart.WindowState = 'Normal' + $formRestart.TopMost = $true + $formRestart.BringToFront() + $formRestart.Location = "$($formInstallationRestartPromptStartPosition.X),$($formInstallationRestartPromptStartPosition.Y)" + $formRestart.Refresh() + [Windows.Forms.Application]::DoEvents() + } + } + [Windows.Forms.Application]::DoEvents() + } + } + + ## Remove all event handlers from the controls + [scriptblock]$Form_Cleanup_FormClosed = { + Try { + $buttonRestartLater.remove_Click($buttonRestartLater_Click) + $buttonRestartNow.remove_Click($buttonRestartNow_Click) + $formRestart.remove_Load($FormEvent_Load) + $formRestart.remove_Resize($formRestart_Resize) + $timerCountdown.remove_Tick($timerCountdown_Tick) + $timerPersist.remove_Tick($timerPersist_Tick) + $formRestart.remove_Load($Form_StateCorrection_Load) + $formRestart.remove_FormClosed($Form_Cleanup_FormClosed) + } + Catch { } + } + + ## Form + If (-not $NoCountdown) { + $formRestart.Controls.Add($labelCountdown) + $formRestart.Controls.Add($labelTimeRemaining) + } + $formRestart.Controls.Add($labelMessage) + $formRestart.Controls.Add($buttonRestartLater) + $formRestart.Controls.Add($picturebox) + $formRestart.Controls.Add($buttonRestartNow) + $clientSizeY = 260 + $appDeployLogoBannerHeightDifference + $formRestart.ClientSize = "450,$clientSizeY" + $formRestart.ControlBox = $false + $formRestart.FormBorderStyle = 'FixedDialog' + $formRestart.Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + $formRestart.MaximizeBox = $false + $formRestart.MinimizeBox = $false + $formRestart.Name = 'formRestart' + $formRestart.StartPosition = 'CenterScreen' + $formRestart.Text = "$($configRestartPromptTitle): $installTitle" + $formRestart.add_Load($FormEvent_Load) + $formRestart.add_Resize($formRestart_Resize) + + ## Banner + $picturebox.Anchor = 'Top' + $picturebox.Image = [Drawing.Image]::Fromfile($AppDeployLogoBanner) + $picturebox.Location = '0,0' + $picturebox.Name = 'picturebox' + $pictureboxSizeY = $appDeployLogoBannerHeight + $picturebox.Size = "450,$pictureboxSizeY" + $picturebox.SizeMode = 'CenterImage' + $picturebox.TabIndex = 1 + $picturebox.TabStop = $false + + ## Label Message + $labelMessageLocationY = 58 + $appDeployLogoBannerHeightDifference + $labelMessage.Location = "20,$labelMessageLocationY" + $labelMessage.Name = 'labelMessage' + $labelMessage.Size = '400,79' + $labelMessage.TabIndex = 3 + $labelMessage.Text = "$configRestartPromptMessage $configRestartPromptMessageTime `n`n$configRestartPromptMessageRestart" + If ($NoCountdown) { $labelMessage.Text = $configRestartPromptMessage } + $labelMessage.TextAlign = 'MiddleCenter' + + ## Label Time Remaining + $labelTimeRemainingLocationY = 138 + $appDeployLogoBannerHeightDifference + $labelTimeRemaining.Location = "20,$labelTimeRemainingLocationY" + $labelTimeRemaining.Name = 'labelTimeRemaining' + $labelTimeRemaining.Size = '400,23' + $labelTimeRemaining.TabIndex = 4 + $labelTimeRemaining.Text = $configRestartPromptTimeRemaining + $labelTimeRemaining.TextAlign = 'MiddleCenter' + + ## Label Countdown + $labelCountdown.Font = 'Microsoft Sans Serif, 18pt, style=Bold' + $labelCountdownLocationY = 165 + $appDeployLogoBannerHeightDifference + $labelCountdown.Location = "20,$labelCountdownLocationY" + $labelCountdown.Name = 'labelCountdown' + $labelCountdown.Size = '400,30' + $labelCountdown.TabIndex = 5 + $labelCountdown.Text = '00:00:00' + $labelCountdown.TextAlign = 'MiddleCenter' + + # Generic Y location for buttons + $buttonsLocationY = 216 + $appDeployLogoBannerHeightDifference + + ## Label Restart Later + $buttonRestartLater.Anchor = 'Bottom,Left' + $buttonRestartLater.Location = "20,$buttonsLocationY" + $buttonRestartLater.Name = 'buttonRestartLater' + $buttonRestartLater.Size = '159,23' + $buttonRestartLater.TabIndex = 0 + $buttonRestartLater.Text = $configRestartPromptButtonRestartLater + $buttonRestartLater.UseVisualStyleBackColor = $true + $buttonRestartLater.add_Click($buttonRestartLater_Click) + + ## Label Restart Now + $buttonRestartNow.Anchor = 'Bottom,Right' + $buttonRestartNow.Location = "265,$buttonsLocationY" + $buttonRestartNow.Name = 'buttonRestartNow' + $buttonRestartNow.Size = '159,23' + $buttonRestartNow.TabIndex = 2 + $buttonRestartNow.Text = $configRestartPromptButtonRestartNow + $buttonRestartNow.UseVisualStyleBackColor = $true + $buttonRestartNow.add_Click($buttonRestartNow_Click) + + ## Timer Countdown + If (-not $NoCountdown) { $timerCountdown.add_Tick($timerCountdown_Tick) } + + ##---------------------------------------------- + + ## Save the initial state of the form + $InitialFormWindowState = $formRestart.WindowState + # Init the OnLoad event to correct the initial state of the form + $formRestart.add_Load($Form_StateCorrection_Load) + # Clean up the control events + $formRestart.add_FormClosed($Form_Cleanup_FormClosed) + $formRestartClosing = [Windows.Forms.FormClosingEventHandler]{ If ($_.CloseReason -eq 'UserClosing') { $_.Cancel = $true } } + $formRestart.add_FormClosing($formRestartClosing) + + ## If the script has been dot-source invoked by the deploy app script, display the restart prompt asynchronously + If ($deployAppScriptFriendlyName) { + If ($NoCountdown) { + Write-Log -Message "Invoking ${CmdletName} asynchronously with no countdown..." -Source ${CmdletName} + } + Else { + Write-Log -Message "Invoking ${CmdletName} asynchronously with a [$countDownSeconds] second countdown..." -Source ${CmdletName} + } + ## Remove Silent reboot parameters from the list that is being forwarded to the main script for asynchronous function execution. This is only for Interactive mode so we dont need silent mode reboot parameters. + $installRestartPromptParameters.Remove("NoSilentRestart") + $installRestartPromptParameters.Remove("SilentCountdownSeconds") + ## Prepare a list of parameters of this function as a string + [string]$installRestartPromptParameters = ($installRestartPromptParameters.GetEnumerator() | ForEach-Object { + If ($_.Value.GetType().Name -eq 'SwitchParameter') { + "-$($_.Key)" + } + ElseIf ($_.Value.GetType().Name -eq 'Boolean') { + "-$($_.Key) `$" + "$($_.Value)".ToLower() + } + ElseIf ($_.Value.GetType().Name -eq 'Int32') { + "-$($_.Key) $($_.Value)" + } + Else { + "-$($_.Key) `"$($_.Value)`"" + } + }) -join ' ' + ## Start another powershell instance silently with function parameters from this function + Start-Process -FilePath "$PSHOME\powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -File `"$scriptPath`" -ReferredInstallTitle `"$installTitle`" -ReferredInstallName `"$installName`" -ReferredLogName `"$logName`" -ShowInstallationRestartPrompt $installRestartPromptParameters -AsyncToolkitLaunch" -WindowStyle 'Hidden' -ErrorAction 'SilentlyContinue' + } + Else { + If ($NoCountdown) { + Write-Log -Message 'Display restart prompt with no countdown.' -Source ${CmdletName} + } + Else { + Write-Log -Message "Display restart prompt with a [$countDownSeconds] second countdown." -Source ${CmdletName} + } + + # Show the Form + Write-Output -InputObject $formRestart.ShowDialog() + $formRestart.Dispose() + + # Activate the Window + [Diagnostics.Process]$powershellProcess = Get-Process | Where-Object { $_.MainWindowTitle -match $installTitle } + [Microsoft.VisualBasic.Interaction]::AppActivate($powershellProcess.ID) + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-BalloonTip +Function Show-BalloonTip { +<# +.SYNOPSIS + Displays a balloon tip notification in the system tray. +.DESCRIPTION + Displays a balloon tip notification in the system tray. +.PARAMETER BalloonTipText + Text of the balloon tip. +.PARAMETER BalloonTipTitle + Title of the balloon tip. +.PARAMETER BalloonTipIcon + Icon to be used. Options: 'Error', 'Info', 'None', 'Warning'. Default is: Info. +.PARAMETER BalloonTipTime + Time in milliseconds to display the balloon tip. Default: 500. +.EXAMPLE + Show-BalloonTip -BalloonTipText 'Installation Started' -BalloonTipTitle 'Application Name' +.EXAMPLE + Show-BalloonTip -BalloonTipIcon 'Info' -BalloonTipText 'Installation Started' -BalloonTipTitle 'Application Name' -BalloonTipTime 1000 +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0)] + [ValidateNotNullOrEmpty()] + [string]$BalloonTipText, + [Parameter(Mandatory=$false,Position=1)] + [ValidateNotNullorEmpty()] + [string]$BalloonTipTitle = $installTitle, + [Parameter(Mandatory=$false,Position=2)] + [ValidateSet('Error','Info','None','Warning')] + [Windows.Forms.ToolTipIcon]$BalloonTipIcon = 'Info', + [Parameter(Mandatory=$false,Position=3)] + [ValidateNotNullorEmpty()] + [int32]$BalloonTipTime = 10000 + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + ## Skip balloon if in silent mode + If (($deployModeSilent) -or (-not $configShowBalloonNotifications) -or (Test-PowerPoint)) { Return } + + ## Dispose of previous balloon + If ($script:notifyIcon) { Try { $script:notifyIcon.Dispose() } Catch {} } + + ## Get the calling function so we know when to display the exiting balloon tip notification in an asynchronous script + Try { [string]$callingFunction = (Get-Variable -Name MyInvocation -Scope 1).Value.Mycommand.Name } Catch { } + + If ($callingFunction -eq 'Exit-Script') { + Write-Log -Message "Display balloon tip notification asynchronously with message [$BalloonTipText]." -Source ${CmdletName} + ## Create a script block to display the balloon notification in a new PowerShell process so that we can wait to cleanly dispose of the balloon tip without having to make the deployment script wait + [scriptblock]$notifyIconScriptBlock = { + Param ( + [Parameter(Mandatory=$true,Position=0)] + [ValidateNotNullOrEmpty()] + [string]$BalloonTipText, + [Parameter(Mandatory=$false,Position=1)] + [ValidateNotNullorEmpty()] + [string]$BalloonTipTitle, + [Parameter(Mandatory=$false,Position=2)] + [ValidateSet('Error','Info','None','Warning')] + $BalloonTipIcon, # Don't strongly type variable as System.Drawing; assembly not loaded yet in asynchronous scriptblock so will throw error + [Parameter(Mandatory=$false,Position=3)] + [ValidateNotNullorEmpty()] + [int32]$BalloonTipTime, + [Parameter(Mandatory=$false,Position=4)] + [ValidateNotNullorEmpty()] + [string]$AppDeployLogoIcon + ) + + ## Load assembly containing class System.Windows.Forms and System.Drawing + Add-Type -AssemblyName 'System.Windows.Forms' -ErrorAction 'Stop' + Add-Type -AssemblyName 'System.Drawing' -ErrorAction 'Stop' + + [Windows.Forms.ToolTipIcon]$BalloonTipIcon = $BalloonTipIcon + $script:notifyIcon = New-Object -TypeName 'System.Windows.Forms.NotifyIcon' -Property @{ + BalloonTipIcon = $BalloonTipIcon + BalloonTipText = $BalloonTipText + BalloonTipTitle = $BalloonTipTitle + Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + Text = -join $BalloonTipText[0..62] + Visible = $true + } + + ## Display the balloon tip notification asynchronously + $script:NotifyIcon.ShowBalloonTip($BalloonTipTime) + + ## Keep the asynchronous PowerShell process running so that we can dispose of the balloon tip icon + Start-Sleep -Milliseconds ($BalloonTipTime) + $script:notifyIcon.Dispose() + } + + ## Invoke a separate PowerShell process passing the script block as a command and associated parameters to display the balloon tip notification asynchronously + Try { + Execute-Process -Path "$PSHOME\powershell.exe" -Parameters "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -Command & {$notifyIconScriptBlock} '$BalloonTipText' '$BalloonTipTitle' '$BalloonTipIcon' '$BalloonTipTime' '$AppDeployLogoIcon'" -NoWait -WindowStyle 'Hidden' -CreateNoWindow + } + Catch { } + } + ## Otherwise create the balloontip icon synchronously + Else { + Write-Log -Message "Display balloon tip notification with message [$BalloonTipText]." -Source ${CmdletName} + [Windows.Forms.ToolTipIcon]$BalloonTipIcon = $BalloonTipIcon + $script:notifyIcon = New-Object -TypeName 'System.Windows.Forms.NotifyIcon' -Property @{ + BalloonTipIcon = $BalloonTipIcon + BalloonTipText = $BalloonTipText + BalloonTipTitle = $BalloonTipTitle + Icon = New-Object -TypeName 'System.Drawing.Icon' -ArgumentList $AppDeployLogoIcon + Text = -join $BalloonTipText[0..62] + Visible = $true + } + + ## Display the balloon tip notification + $script:NotifyIcon.ShowBalloonTip($BalloonTipTime) + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Show-InstallationProgress +Function Show-InstallationProgress { +<# +.SYNOPSIS + Displays a progress dialog in a separate thread with an updateable custom message. +.DESCRIPTION + Create a WPF window in a separate thread to display a marquee style progress ellipse with a custom message that can be updated. + The status message supports line breaks. + The first time this function is called in a script, it will display a balloon tip notification to indicate that the installation has started (provided balloon tips are enabled in the configuration). +.PARAMETER StatusMessage + The status message to be displayed. The default status message is taken from the XML configuration file. +.PARAMETER WindowLocation + The location of the progress window. Default: just below top, centered. +.PARAMETER TopMost + Specifies whether the progress window should be topmost. Default: $true. +.EXAMPLE + Show-InstallationProgress + Uses the default status message from the XML configuration file. +.EXAMPLE + Show-InstallationProgress -StatusMessage 'Installation in Progress...' +.EXAMPLE + Show-InstallationProgress -StatusMessage "Installation in Progress...`nThe installation may take 20 minutes to complete." +.EXAMPLE + Show-InstallationProgress -StatusMessage 'Installation in Progress...' -WindowLocation 'BottomRight' -TopMost $false +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$StatusMessage = $configProgressMessageInstall, + [Parameter(Mandatory=$false)] + [ValidateSet('Default','BottomRight','TopCenter')] + [string]$WindowLocation = 'Default', + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$TopMost = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If ($deployModeSilent) { Return } + + ## If the default progress message hasn't been overridden and the deployment type is uninstall, use the default uninstallation message + If (($StatusMessage -eq $configProgressMessageInstall) -and ($deploymentType -eq 'Uninstall')) { + $StatusMessage = $configProgressMessageUninstall + } + If (($StatusMessage -eq $configProgressMessageInstall) -and ($deploymentType -eq 'Repair')) { + $StatusMessage = $configProgressMessageRepair + } + + If ($envHost.Name -match 'PowerGUI') { + Write-Log -Message "$($envHost.Name) is not a supported host for WPF multi-threading. Progress dialog with message [$statusMessage] will not be displayed." -Severity 2 -Source ${CmdletName} + Return + } + + ## Check if the progress thread is running before invoking methods on it + If ($script:ProgressSyncHash.Window.Dispatcher.Thread.ThreadState -ne 'Running') { + # Notify user that the software installation has started + $balloonText = "$deploymentTypeName $configBalloonTextStart" + Show-BalloonTip -BalloonTipIcon 'Info' -BalloonTipText $balloonText + # Create a synchronized hashtable to share objects between runspaces + $script:ProgressSyncHash = [hashtable]::Synchronized(@{ }) + # Create a new runspace for the progress bar + $script:ProgressRunspace = [runspacefactory]::CreateRunspace() + $script:ProgressRunspace.ApartmentState = 'STA' + $script:ProgressRunspace.ThreadOptions = 'ReuseThread' + $script:ProgressRunspace.Open() + # Add the sync hash to the runspace + $script:ProgressRunspace.SessionStateProxy.SetVariable('progressSyncHash', $script:ProgressSyncHash) + # Add other variables from the parent thread required in the progress runspace + $script:ProgressRunspace.SessionStateProxy.SetVariable('installTitle', $installTitle) + $script:ProgressRunspace.SessionStateProxy.SetVariable('windowLocation', $windowLocation) + $script:ProgressRunspace.SessionStateProxy.SetVariable('topMost', $topMost.ToString()) + $script:ProgressRunspace.SessionStateProxy.SetVariable('appDeployLogoBanner', $appDeployLogoBanner) + $script:ProgressRunspace.SessionStateProxy.SetVariable('ProgressStatusMessage', $statusMessage) + $script:ProgressRunspace.SessionStateProxy.SetVariable('AppDeployLogoIcon', $AppDeployLogoIcon) + + # Add the script block to be executed in the progress runspace + $progressCmd = [PowerShell]::Create().AddScript({ + [string]$xamlProgressString = @' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'@ + [Xml.XmlDocument]$xamlProgress = New-Object 'System.Xml.XmlDocument' + $xamlProgress.LoadXml($xamlProgressString) + ## Set the configurable values using variables added to the runspace from the parent thread + $xamlProgress.Window.TopMost = $topMost + $xamlProgress.Window.Icon = $AppDeployLogoIcon + $xamlProgress.Window.Grid.Image.Source = $appDeployLogoBanner + $xamlProgress.Window.Grid.TextBlock.Text = $ProgressStatusMessage + $xamlProgress.Window.Title = $installTitle + # Parse the XAML + $progressReader = New-Object -TypeName 'System.Xml.XmlNodeReader' -ArgumentList $xamlProgress + $script:ProgressSyncHash.Window = [Windows.Markup.XamlReader]::Load($progressReader) + # Grey out the X button + $script:ProgressSyncHash.Window.add_Loaded({ + # Calculate the position on the screen where the progress dialog should be placed + [int32]$screenWidth = [System.Windows.SystemParameters]::WorkArea.Width + [int32]$screenHeight = [System.Windows.SystemParameters]::WorkArea.Height + [int32]$screenCenterWidth = $screenWidth - $script:ProgressSyncHash.Window.ActualWidth + [int32]$screenCenterHeight = $screenHeight - $script:ProgressSyncHash.Window.ActualHeight + # Set the start position of the Window based on the screen size + If ($windowLocation -eq 'BottomRight') { + # Put the window in the corner + $script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth) + $script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight) + } + ElseIf($windowLocation -eq 'TopCenter'){ + $script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth / 2) + $script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight / 6) + } + Else { + # Center the progress window by calculating the center of the workable screen based on the width of the screen minus half the width of the progress bar + $script:ProgressSyncHash.Window.Left = [Double]($screenCenterWidth / 2) + $script:ProgressSyncHash.Window.Top = [Double]($screenCenterHeight / 2) + } + # Grey out the X button + try { + $windowHandle = (New-Object -TypeName System.Windows.Interop.WindowInteropHelper -ArgumentList $this).Handle + If ($windowHandle -and ($windowHandle -ne [IntPtr]::Zero)) { + $menuHandle = [PSADT.UiAutomation]::GetSystemMenu($windowHandle, $false) + If ($menuHandle -and ($menuHandle -ne [IntPtr]::Zero)) { + [PSADT.UiAutomation]::EnableMenuItem($menuHandle, 0xF060, 0x00000001) + [PSADT.UiAutomation]::DestroyMenu($menuHandle) + } + } + } + catch { + # Not a terminating error if we can't grey out the button + Write-Log "Failed to grey out the Close button." -Severity 2 -Source ${CmdletName} + } + }) + # Prepare the ProgressText variable so we can use it to change the text in the text area + $script:ProgressSyncHash.ProgressText = $script:ProgressSyncHash.Window.FindName('ProgressText') + # Add an action to the Window.Closing event handler to disable the close button + $script:ProgressSyncHash.Window.Add_Closing({ $_.Cancel = $true }) + # Allow the window to be dragged by clicking on it anywhere + $script:ProgressSyncHash.Window.Add_MouseLeftButtonDown({ $script:ProgressSyncHash.Window.DragMove() }) + # Add a tooltip + $script:ProgressSyncHash.Window.ToolTip = $installTitle + $null = $script:ProgressSyncHash.Window.ShowDialog() + $script:ProgressSyncHash.Error = $Error + }) + + $progressCmd.Runspace = $script:ProgressRunspace + Write-Log -Message "Spin up progress dialog in a separate thread with message: [$statusMessage]." -Source ${CmdletName} + # Invoke the progress runspace + $null = $progressCmd.BeginInvoke() + # Allow the thread to be spun up safely before invoking actions against it. + Start-Sleep -Seconds 1 + If ($script:ProgressSyncHash.Error) { + Write-Log -Message "Failure while displaying progress dialog. `n$(Resolve-Error -ErrorRecord $script:ProgressSyncHash.Error)" -Severity 3 -Source ${CmdletName} + } + } + ## Check if the progress thread is running before invoking methods on it + ElseIf ($script:ProgressSyncHash.Window.Dispatcher.Thread.ThreadState -eq 'Running') { + # Update the progress text + Try { + $script:ProgressSyncHash.Window.Dispatcher.Invoke([Windows.Threading.DispatcherPriority]'Send', [Windows.Input.InputEventHandler]{ $script:ProgressSyncHash.ProgressText.Text = $statusMessage }, $null, $null) + Write-Log -Message "Updated progress message: [$statusMessage]." -Source ${CmdletName} + } + Catch { + Write-Log -Message "Unable to update the progress message. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Close-InstallationProgress +Function Close-InstallationProgress { +<# +.SYNOPSIS + Closes the dialog created by Show-InstallationProgress. +.DESCRIPTION + Closes the dialog created by Show-InstallationProgress. + This function is called by the Exit-Script function to close a running instance of the progress dialog if found. +.EXAMPLE + Close-InstallationProgress +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + If ($script:ProgressSyncHash.Window.Dispatcher.Thread.ThreadState -eq 'Running') { + ## Close the progress thread + Write-Log -Message 'Close the installation progress dialog.' -Source ${CmdletName} + $script:ProgressSyncHash.Window.Dispatcher.InvokeShutdown() + $script:ProgressSyncHash.Clear() + $script:ProgressRunspace.Close() + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-PinnedApplication +Function Set-PinnedApplication { +<# +.SYNOPSIS + Pins or unpins a shortcut to the start menu or task bar. +.DESCRIPTION + Pins or unpins a shortcut to the start menu or task bar. + This should typically be run in the user context, as pinned items are stored in the user profile. +.PARAMETER Action + Action to be performed. Options: 'PintoStartMenu','UnpinfromStartMenu','PintoTaskbar','UnpinfromTaskbar'. +.PARAMETER FilePath + Path to the shortcut file to be pinned or unpinned. +.EXAMPLE + Set-PinnedApplication -Action 'PintoStartMenu' -FilePath "$envProgramFilesX86\IBM\Lotus\Notes\notes.exe" +.EXAMPLE + Set-PinnedApplication -Action 'UnpinfromTaskbar' -FilePath "$envProgramFilesX86\IBM\Lotus\Notes\notes.exe" +.NOTES + Windows 10 logic borrowed from Stuart Pearson (https://pinto10blog.wordpress.com/2016/09/10/pinto10/) +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateSet('PintoStartMenu','UnpinfromStartMenu','PintoTaskbar','UnpinfromTaskbar')] + [string]$Action, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$FilePath + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + #region Function Get-PinVerb + Function Get-PinVerb { + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [int32]$VerbId + ) + + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + Write-Log -Message "Get localized pin verb for verb id [$VerbID]." -Source ${CmdletName} + [string]$PinVerb = [PSADT.FileVerb]::GetPinVerb($VerbId) + Write-Log -Message "Verb ID [$VerbID] has a localized pin verb of [$PinVerb]." -Source ${CmdletName} + Write-Output -InputObject $PinVerb + } + #endregion + + #region Function Invoke-Verb + Function Invoke-Verb { + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$FilePath, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Verb + ) + + Try { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + $verb = $verb.Replace('&','') + $path = Split-Path -Path $FilePath -Parent -ErrorAction 'Stop' + $folder = $shellApp.Namespace($path) + $item = $folder.ParseName((Split-Path -Path $FilePath -Leaf -ErrorAction 'Stop')) + $itemVerb = $item.Verbs() | Where-Object { $_.Name.Replace('&','') -eq $verb } -ErrorAction 'Stop' + + If ($null -eq $itemVerb) { + Write-Log -Message "Performing action [$verb] is not programmatically supported for this file [$FilePath]." -Severity 2 -Source ${CmdletName} + } + Else { + Write-Log -Message "Perform action [$verb] on [$FilePath]." -Source ${CmdletName} + $itemVerb.DoIt() + } + } + Catch { + Write-Log -Message "Failed to perform action [$verb] on [$FilePath]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + #endregion + + If (([version]$envOSVersion).Major -ge 10) { + Write-Log -Message "Detected Windows 10 or higher, using Windows 10 verb codes." -Source ${CmdletName} + [hashtable]$Verbs = @{ + 'PintoStartMenu' = 51201 + 'UnpinfromStartMenu' = 51394 + 'PintoTaskbar' = 5386 + 'UnpinfromTaskbar' = 5387 + } + } + Else { + [hashtable]$Verbs = @{ + 'PintoStartMenu' = 5381 + 'UnpinfromStartMenu' = 5382 + 'PintoTaskbar' = 5386 + 'UnpinfromTaskbar' = 5387 + } + } + + } + Process { + Try { + Write-Log -Message "Execute action [$Action] for file [$FilePath]." -Source ${CmdletName} + + If (-not (Test-Path -LiteralPath $FilePath -PathType 'Leaf' -ErrorAction 'Stop')) { + Throw "Path [$filePath] does not exist." + } + + If (-not ($Verbs.$Action)) { + Throw "Action [$Action] not supported. Supported actions are [$($Verbs.Keys -join ', ')]." + } + + If ($Action.Contains("StartMenu")) + { + If ([int]$envOSVersionMajor -ge 10) { + If ((Get-Item -Path $FilePath).Extension -ne '.lnk') { + Throw "Only shortcut files (.lnk) are supported on Windows 10 and higher." + } + ElseIf (-not ($FilePath.StartsWith($envUserStartMenu, 'CurrentCultureIgnoreCase') -or $FilePath.StartsWith($envCommonStartMenu, 'CurrentCultureIgnoreCase'))) { + Throw "Only shortcut files (.lnk) in [$envUserStartMenu] and [$envCommonStartMenu] are supported on Windows 10 and higher." + } + } + + [string]$PinVerbAction = Get-PinVerb -VerbId $Verbs.$Action + If (-not ($PinVerbAction)) { + Throw "Failed to get a localized pin verb for action [$Action]. Action is not supported on this operating system." + } + + Invoke-Verb -FilePath $FilePath -Verb $PinVerbAction + } + ElseIf ($Action.Contains("Taskbar")) { + If ([int]$envOSVersionMajor -ge 10) { + $FileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($FilePath) + $PinExists = Test-Path -Path "$envAppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$($FileNameWithoutExtension).lnk" + + If ($Action -eq 'PintoTaskbar' -and $PinExists) { + If($(Invoke-ObjectMethod -InputObject $Shell -MethodName 'CreateShortcut' -ArgumentList "$envAppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$($FileNameWithoutExtension).lnk").TargetPath -eq $FilePath) { + Write-Log -Message "Pin [$FileNameWithoutExtension] already exists." -Source ${CmdletName} + return + } + } + ElseIf ($Action -eq 'UnpinfromTaskbar' -and $PinExists -eq $false) { + Write-Log -Message "Pin [$FileNameWithoutExtension] does not exist." -Source ${CmdletName} + return + } + + $ExplorerCommandHandler = Get-RegistryKey -Key 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin' -Value 'ExplorerCommandHandler' + $classesStarKey = (Get-Item "Registry::HKEY_USERS\$($RunasActiveUser.SID)\SOFTWARE\Classes").OpenSubKey("*", $true) + $shellKey = $classesStarKey.CreateSubKey("shell", $true) + $specialKey = $shellKey.CreateSubKey("{:}", $true) + $specialKey.SetValue("ExplorerCommandHandler", $ExplorerCommandHandler) + + $Folder = Invoke-ObjectMethod -InputObject $ShellApp -MethodName 'Namespace' -ArgumentList $(Split-Path -Path $FilePath -Parent) + $Item = Invoke-ObjectMethod -InputObject $Folder -MethodName 'ParseName' -ArgumentList $(Split-Path -Path $FilePath -Leaf) + + $Item.InvokeVerb("{:}") + + $shellKey.DeleteSubKey("{:}") + If ($shellKey.SubKeyCount -eq 0 -and $shellKey.ValueCount -eq 0) { + $classesStarKey.DeleteSubKey("shell") + } + } + Else { + [string]$PinVerbAction = Get-PinVerb -VerbId $Verbs.$Action + If (-not ($PinVerbAction)) { + Throw "Failed to get a localized pin verb for action [$Action]. Action is not supported on this operating system." + } + + Invoke-Verb -FilePath $FilePath -Verb $PinVerbAction + } + } + } + Catch { + Write-Log -Message "Failed to execute action [$Action]. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + Finally { + Try { If ($shellKey) { $shellKey.Close() } } Catch { } + Try { If ($classesStarKey) { $classesStarKey.Close() } } Catch { } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-IniValue +Function Get-IniValue { +<# +.SYNOPSIS + Parses an INI file and returns the value of the specified section and key. +.DESCRIPTION + Parses an INI file and returns the value of the specified section and key. +.PARAMETER FilePath + Path to the INI file. +.PARAMETER Section + Section within the INI file. +.PARAMETER Key + Key within the section of the INI file. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-IniValue -FilePath "$envProgramFilesX86\IBM\Notes\notes.ini" -Section 'Notes' -Key 'KeyFileName' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$FilePath, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Section, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Read INI Key: [Section = $Section] [Key = $Key]." -Source ${CmdletName} + + If (-not (Test-Path -LiteralPath $FilePath -PathType 'Leaf')) { Throw "File [$filePath] could not be found." } + + $IniValue = [PSADT.IniFile]::GetIniValue($Section, $Key, $FilePath) + Write-Log -Message "INI Key Value: [Section = $Section] [Key = $Key] [Value = $IniValue]." -Source ${CmdletName} + + Write-Output -InputObject $IniValue + } + Catch { + Write-Log -Message "Failed to read INI file key value. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to read INI file key value: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-IniValue +Function Set-IniValue { +<# +.SYNOPSIS + Opens an INI file and sets the value of the specified section and key. +.DESCRIPTION + Opens an INI file and sets the value of the specified section and key. +.PARAMETER FilePath + Path to the INI file. +.PARAMETER Section + Section within the INI file. +.PARAMETER Key + Key within the section of the INI file. +.PARAMETER Value + Value for the key within the section of the INI file. To remove a value, set this variable to $null. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Set-IniValue -FilePath "$envProgramFilesX86\IBM\Notes\notes.ini" -Section 'Notes' -Key 'KeyFileName' -Value 'MyFile.ID' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$FilePath, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Section, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Key, + # Don't strongly type this variable as [string] b/c PowerShell replaces [string]$Value = $null with an empty string + [Parameter(Mandatory=$true)] + [AllowNull()] + $Value, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Write INI Key Value: [Section = $Section] [Key = $Key] [Value = $Value]." -Source ${CmdletName} + + If (-not (Test-Path -LiteralPath $FilePath -PathType 'Leaf')) { Throw "File [$filePath] could not be found." } + + [PSADT.IniFile]::SetIniValue($Section, $Key, ([Text.StringBuilder]$Value), $FilePath) + } + Catch { + Write-Log -Message "Failed to write INI file key value. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to write INI file key value: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-PEFileArchitecture +Function Get-PEFileArchitecture { +<# +.SYNOPSIS + Determine if a PE file is a 32-bit or a 64-bit file. +.DESCRIPTION + Determine if a PE file is a 32-bit or a 64-bit file by examining the file's image file header. + PE file extensions: .exe, .dll, .ocx, .drv, .sys, .scr, .efi, .cpl, .fon +.PARAMETER FilePath + Path to the PE file to examine. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.PARAMETER PassThru + Get the file object, attach a property indicating the file binary type, and write to pipeline +.EXAMPLE + Get-PEFileArchitecture -FilePath "$env:windir\notepad.exe" +.NOTES + This is an internal script function and should typically not be called directly. +.LINK +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [IO.FileInfo[]]$FilePath, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true, + [Parameter(Mandatory=$false)] + [switch]$PassThru + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + [string[]]$PEFileExtensions = '.exe', '.dll', '.ocx', '.drv', '.sys', '.scr', '.efi', '.cpl', '.fon' + [int32]$MACHINE_OFFSET = 4 + [int32]$PE_POINTER_OFFSET = 60 + } + Process { + ForEach ($Path in $filePath) { + Try { + If ($PEFileExtensions -notcontains $Path.Extension) { + Throw "Invalid file type. Please specify one of the following PE file types: $($PEFileExtensions -join ', ')" + } + + [byte[]]$data = New-Object -TypeName 'System.Byte[]' -ArgumentList 4096 + $stream = New-Object -TypeName 'System.IO.FileStream' -ArgumentList ($Path.FullName, 'Open', 'Read') + $null = $stream.Read($data, 0, 4096) + $stream.Flush() + $stream.Close() + + [int32]$PE_HEADER_ADDR = [BitConverter]::ToInt32($data, $PE_POINTER_OFFSET) + [uint16]$PE_IMAGE_FILE_HEADER = [BitConverter]::ToUInt16($data, $PE_HEADER_ADDR + $MACHINE_OFFSET) + Switch ($PE_IMAGE_FILE_HEADER) { + 0 { $PEArchitecture = 'Native' } # The contents of this file are assumed to be applicable to any machine type + 0x014c { $PEArchitecture = '32BIT' } # File for Windows 32-bit systems + 0x0200 { $PEArchitecture = 'Itanium-x64' } # File for Intel Itanium x64 processor family + 0x8664 { $PEArchitecture = '64BIT' } # File for Windows 64-bit systems + Default { $PEArchitecture = 'Unknown' } + } + Write-Log -Message "File [$($Path.FullName)] has a detected file architecture of [$PEArchitecture]." -Source ${CmdletName} + + If ($PassThru) { + # Get the file object, attach a property indicating the type, and write to pipeline + Get-Item -LiteralPath $Path.FullName -Force | Add-Member -MemberType 'NoteProperty' -Name 'BinaryType' -Value $PEArchitecture -Force -PassThru | Write-Output + } + Else { + Write-Output -InputObject $PEArchitecture + } + } + Catch { + Write-Log -Message "Failed to get the PE file architecture. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to get the PE file architecture: $($_.Exception.Message)" + } + Continue + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Invoke-RegisterOrUnregisterDLL +Function Invoke-RegisterOrUnregisterDLL { +<# +.SYNOPSIS + Register or unregister a DLL file. +.DESCRIPTION + Register or unregister a DLL file using regsvr32.exe. Function can be invoked using alias: 'Register-DLL' or 'Unregister-DLL'. +.PARAMETER FilePath + Path to the DLL file. +.PARAMETER DLLAction + Specify whether to register or unregister the DLL. Optional if function is invoked using 'Register-DLL' or 'Unregister-DLL' alias. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Register-DLL -FilePath "C:\Test\DcTLSFileToDMSComp.dll" + Register DLL file using the "Register-DLL" alias for this function +.EXAMPLE + UnRegister-DLL -FilePath "C:\Test\DcTLSFileToDMSComp.dll" + Unregister DLL file using the "Unregister-DLL" alias for this function +.EXAMPLE + Invoke-RegisterOrUnregisterDLL -FilePath "C:\Test\DcTLSFileToDMSComp.dll" -DLLAction 'Register' + Register DLL file using the actual name of this function +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$FilePath, + [Parameter(Mandatory=$false)] + [ValidateSet('Register','Unregister')] + [string]$DLLAction, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Get name used to invoke this function in case the 'Register-DLL' or 'Unregister-DLL' alias was used and set the correct DLL action + [string]${InvokedCmdletName} = $MyInvocation.InvocationName + # Set the correct register/unregister action based on the alias used to invoke this function + If (${InvokedCmdletName} -ne ${CmdletName}) { + Switch (${InvokedCmdletName}) { + 'Register-DLL' { [string]$DLLAction = 'Register' } + 'Unregister-DLL' { [string]$DLLAction = 'Unregister' } + } + } + # Set the correct DLL register/unregister action parameters + If (-not $DLLAction) { Throw 'Parameter validation failed. Please specify the [-DLLAction] parameter to determine whether to register or unregister the DLL.' } + [string]$DLLAction = ((Get-Culture).TextInfo).ToTitleCase($DLLAction.ToLower()) + Switch ($DLLAction) { + 'Register' { [string]$DLLActionParameters = "/s `"$FilePath`"" } + 'Unregister' { [string]$DLLActionParameters = "/s /u `"$FilePath`"" } + } + } + Process { + Try { + Write-Log -Message "$DLLAction DLL file [$filePath]." -Source ${CmdletName} + If (-not (Test-Path -LiteralPath $FilePath -PathType 'Leaf')) { Throw "File [$filePath] could not be found." } + + [string]$DLLFileBitness = Get-PEFileArchitecture -FilePath $filePath -ContinueOnError $false -ErrorAction 'Stop' + If (($DLLFileBitness -ne '64BIT') -and ($DLLFileBitness -ne '32BIT')) { + Throw "File [$filePath] has a detected file architecture of [$DLLFileBitness]. Only 32-bit or 64-bit DLL files can be $($DLLAction.ToLower() + 'ed')." + } + + If ($Is64Bit) { + If ($DLLFileBitness -eq '64BIT') { + If ($Is64BitProcess) { + [string]$RegSvr32Path = "$envWinDir\system32\regsvr32.exe" + } + Else { + [string]$RegSvr32Path = "$envWinDir\sysnative\regsvr32.exe" + } + } + ElseIf ($DLLFileBitness -eq '32BIT') { + [string]$RegSvr32Path = "$envWinDir\SysWOW64\regsvr32.exe" + } + } + Else { + If ($DLLFileBitness -eq '64BIT') { + Throw "File [$filePath] cannot be $($DLLAction.ToLower()) because it is a 64-bit file on a 32-bit operating system." + } + ElseIf ($DLLFileBitness -eq '32BIT') { + [string]$RegSvr32Path = "$envWinDir\system32\regsvr32.exe" + } + } + + [psobject]$ExecuteResult = Execute-Process -Path $RegSvr32Path -Parameters $DLLActionParameters -WindowStyle 'Hidden' -PassThru -ExitOnProcessFailure $false + + If ($ExecuteResult.ExitCode -ne 0) { + If ($ExecuteResult.ExitCode -eq 60002) { + Throw "Execute-Process function failed with exit code [$($ExecuteResult.ExitCode)]." + } + Else { + Throw "regsvr32.exe failed with exit code [$($ExecuteResult.ExitCode)]." + } + } + } + Catch { + Write-Log -Message "Failed to $($DLLAction.ToLower()) DLL file. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to $($DLLAction.ToLower()) DLL file: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +Set-Alias -Name 'Register-DLL' -Value 'Invoke-RegisterOrUnregisterDLL' -Scope 'Script' -Force -ErrorAction 'SilentlyContinue' +Set-Alias -Name 'Unregister-DLL' -Value 'Invoke-RegisterOrUnregisterDLL' -Scope 'Script' -Force -ErrorAction 'SilentlyContinue' +#endregion + + +#region Function Invoke-ObjectMethod +Function Invoke-ObjectMethod { +<# +.SYNOPSIS + Invoke method on any object. +.DESCRIPTION + Invoke method on any object with or without using named parameters. +.PARAMETER InputObject + Specifies an object which has methods that can be invoked. +.PARAMETER MethodName + Specifies the name of a method to invoke. +.PARAMETER ArgumentList + Argument to pass to the method being executed. Allows execution of method without specifying named parameters. +.PARAMETER Parameter + Argument to pass to the method being executed. Allows execution of method by using named parameters. +.EXAMPLE + $ShellApp = New-Object -ComObject 'Shell.Application' + $null = Invoke-ObjectMethod -InputObject $ShellApp -MethodName 'MinimizeAll' + Minimizes all windows. +.EXAMPLE + $ShellApp = New-Object -ComObject 'Shell.Application' + $null = Invoke-ObjectMethod -InputObject $ShellApp -MethodName 'Explore' -Parameter @{'vDir'='C:\Windows'} + Opens the C:\Windows folder in a Windows Explorer window. +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding(DefaultParameterSetName='Positional')] + Param ( + [Parameter(Mandatory=$true,Position=0)] + [ValidateNotNull()] + [object]$InputObject, + [Parameter(Mandatory=$true,Position=1)] + [ValidateNotNullorEmpty()] + [string]$MethodName, + [Parameter(Mandatory=$false,Position=2,ParameterSetName='Positional')] + [object[]]$ArgumentList, + [Parameter(Mandatory=$true,Position=2,ParameterSetName='Named')] + [ValidateNotNull()] + [hashtable]$Parameter + ) + + Begin { } + Process { + If ($PSCmdlet.ParameterSetName -eq 'Named') { + ## Invoke method by using parameter names + Write-Output -InputObject $InputObject.GetType().InvokeMember($MethodName, [Reflection.BindingFlags]::InvokeMethod, $null, $InputObject, ([object[]]($Parameter.Values)), $null, $null, ([string[]]($Parameter.Keys))) + } + Else { + ## Invoke method without using parameter names + Write-Output -InputObject $InputObject.GetType().InvokeMember($MethodName, [Reflection.BindingFlags]::InvokeMethod, $null, $InputObject, $ArgumentList, $null, $null, $null) + } + } + End { } +} +#endregion + + +#region Function Get-ObjectProperty +Function Get-ObjectProperty { +<# +.SYNOPSIS + Get a property from any object. +.DESCRIPTION + Get a property from any object. +.PARAMETER InputObject + Specifies an object which has properties that can be retrieved. +.PARAMETER PropertyName + Specifies the name of a property to retrieve. +.PARAMETER ArgumentList + Argument to pass to the property being retrieved. +.EXAMPLE + Get-ObjectProperty -InputObject $Record -PropertyName 'StringData' -ArgumentList @(1) +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0)] + [ValidateNotNull()] + [object]$InputObject, + [Parameter(Mandatory=$true,Position=1)] + [ValidateNotNullorEmpty()] + [string]$PropertyName, + [Parameter(Mandatory=$false,Position=2)] + [object[]]$ArgumentList + ) + + Begin { } + Process { + ## Retrieve property + Write-Output -InputObject $InputObject.GetType().InvokeMember($PropertyName, [Reflection.BindingFlags]::GetProperty, $null, $InputObject, $ArgumentList, $null, $null, $null) + } + End { } +} +#endregion + + +#region Function Get-MsiTableProperty +Function Get-MsiTableProperty { +<# +.SYNOPSIS + Get all of the properties from a Windows Installer database table or the Summary Information stream and return as a custom object. +.DESCRIPTION + Use the Windows Installer object to read all of the properties from a Windows Installer database table or the Summary Information stream. +.PARAMETER Path + The fully qualified path to an database file. Supports .msi and .msp files. +.PARAMETER TransformPath + The fully qualified path to a list of MST file(s) which should be applied to the MSI file. +.PARAMETER Table + The name of the the MSI table from which all of the properties must be retrieved. Default is: 'Property'. +.PARAMETER TablePropertyNameColumnNum + Specify the table column number which contains the name of the properties. Default is: 1 for MSIs and 2 for MSPs. +.PARAMETER TablePropertyValueColumnNum + Specify the table column number which contains the value of the properties. Default is: 2 for MSIs and 3 for MSPs. +.PARAMETER GetSummaryInformation + Retrieves the Summary Information for the Windows Installer database. + Summary Information property descriptions: https://msdn.microsoft.com/en-us/library/aa372049(v=vs.85).aspx +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-MsiTableProperty -Path 'C:\Package\AppDeploy.msi' -TransformPath 'C:\Package\AppDeploy.mst' + Retrieve all of the properties from the default 'Property' table. +.EXAMPLE + Get-MsiTableProperty -Path 'C:\Package\AppDeploy.msi' -TransformPath 'C:\Package\AppDeploy.mst' -Table 'Property' | Select-Object -ExpandProperty ProductCode + Retrieve all of the properties from the 'Property' table and then pipe to Select-Object to select the ProductCode property. +.EXAMPLE + Get-MsiTableProperty -Path 'C:\Package\AppDeploy.msi' -GetSummaryInformation + Retrieves the Summary Information for the Windows Installer database. +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding(DefaultParameterSetName='TableInfo')] + Param ( + [Parameter(Mandatory=$true)] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [string]$Path, + [Parameter(Mandatory=$false)] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [string[]]$TransformPath, + [Parameter(Mandatory=$false,ParameterSetName='TableInfo')] + [ValidateNotNullOrEmpty()] + [string]$Table = $(If ([IO.Path]::GetExtension($Path) -eq '.msi') { 'Property' } Else { 'MsiPatchMetadata' }), + [Parameter(Mandatory=$false,ParameterSetName='TableInfo')] + [ValidateNotNullorEmpty()] + [int32]$TablePropertyNameColumnNum = $(If ([IO.Path]::GetExtension($Path) -eq '.msi') { 1 } Else { 2 }), + [Parameter(Mandatory=$false,ParameterSetName='TableInfo')] + [ValidateNotNullorEmpty()] + [int32]$TablePropertyValueColumnNum = $(If ([IO.Path]::GetExtension($Path) -eq '.msi') { 2 } Else { 3 }), + [Parameter(Mandatory=$true,ParameterSetName='SummaryInfo')] + [ValidateNotNullorEmpty()] + [switch]$GetSummaryInformation = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + If ($PSCmdlet.ParameterSetName -eq 'TableInfo') { + Write-Log -Message "Read data from Windows Installer database file [$Path] in table [$Table]." -Source ${CmdletName} + } + Else { + Write-Log -Message "Read the Summary Information from the Windows Installer database file [$Path]." -Source ${CmdletName} + } + + ## Create a Windows Installer object + [__comobject]$Installer = New-Object -ComObject 'WindowsInstaller.Installer' -ErrorAction 'Stop' + ## Determine if the database file is a patch (.msp) or not + If ([IO.Path]::GetExtension($Path) -eq '.msp') { [boolean]$IsMspFile = $true } + ## Define properties for how the MSI database is opened + [int32]$msiOpenDatabaseModeReadOnly = 0 + [int32]$msiSuppressApplyTransformErrors = 63 + [int32]$msiOpenDatabaseMode = $msiOpenDatabaseModeReadOnly + [int32]$msiOpenDatabaseModePatchFile = 32 + If ($IsMspFile) { [int32]$msiOpenDatabaseMode = $msiOpenDatabaseModePatchFile } + ## Open database in read only mode + [__comobject]$Database = Invoke-ObjectMethod -InputObject $Installer -MethodName 'OpenDatabase' -ArgumentList @($Path, $msiOpenDatabaseMode) + ## Apply a list of transform(s) to the database + If (($TransformPath) -and (-not $IsMspFile)) { + ForEach ($Transform in $TransformPath) { + $null = Invoke-ObjectMethod -InputObject $Database -MethodName 'ApplyTransform' -ArgumentList @($Transform, $msiSuppressApplyTransformErrors) + } + } + + ## Get either the requested windows database table information or summary information + If ($PSCmdlet.ParameterSetName -eq 'TableInfo') { + ## Open the requested table view from the database + [__comobject]$View = Invoke-ObjectMethod -InputObject $Database -MethodName 'OpenView' -ArgumentList @("SELECT * FROM $Table") + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Execute' + + ## Create an empty object to store properties in + [psobject]$TableProperties = New-Object -TypeName 'PSObject' + + ## Retrieve the first row from the requested table. If the first row was successfully retrieved, then save data and loop through the entire table. + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa371136(v=vs.85).aspx + [__comobject]$Record = Invoke-ObjectMethod -InputObject $View -MethodName 'Fetch' + While ($Record) { + # Read string data from record and add property/value pair to custom object + $TableProperties | Add-Member -MemberType 'NoteProperty' -Name (Get-ObjectProperty -InputObject $Record -PropertyName 'StringData' -ArgumentList @($TablePropertyNameColumnNum)) -Value (Get-ObjectProperty -InputObject $Record -PropertyName 'StringData' -ArgumentList @($TablePropertyValueColumnNum)) -Force + # Retrieve the next row in the table + [__comobject]$Record = Invoke-ObjectMethod -InputObject $View -MethodName 'Fetch' + } + Write-Output -InputObject $TableProperties + } + Else { + ## Get the SummaryInformation from the windows installer database + [__comobject]$SummaryInformation = Get-ObjectProperty -InputObject $Database -PropertyName 'SummaryInformation' + [hashtable]$SummaryInfoProperty = @{} + ## Summary property descriptions: https://msdn.microsoft.com/en-us/library/aa372049(v=vs.85).aspx + $SummaryInfoProperty.Add('CodePage', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(1))) + $SummaryInfoProperty.Add('Title', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(2))) + $SummaryInfoProperty.Add('Subject', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(3))) + $SummaryInfoProperty.Add('Author', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(4))) + $SummaryInfoProperty.Add('Keywords', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(5))) + $SummaryInfoProperty.Add('Comments', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(6))) + $SummaryInfoProperty.Add('Template', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(7))) + $SummaryInfoProperty.Add('LastSavedBy', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(8))) + $SummaryInfoProperty.Add('RevisionNumber', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(9))) + $SummaryInfoProperty.Add('LastPrinted', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(11))) + $SummaryInfoProperty.Add('CreateTimeDate', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(12))) + $SummaryInfoProperty.Add('LastSaveTimeDate', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(13))) + $SummaryInfoProperty.Add('PageCount', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(14))) + $SummaryInfoProperty.Add('WordCount', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(15))) + $SummaryInfoProperty.Add('CharacterCount', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(16))) + $SummaryInfoProperty.Add('CreatingApplication', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(18))) + $SummaryInfoProperty.Add('Security', (Get-ObjectProperty -InputObject $SummaryInformation -PropertyName 'Property' -ArgumentList @(19))) + [psobject]$SummaryInfoProperties = New-Object -TypeName 'PSObject' -Property $SummaryInfoProperty + Write-Output -InputObject $SummaryInfoProperties + } + } + Catch { + Write-Log -Message "Failed to get the MSI table [$Table]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to get the MSI table [$Table]: $($_.Exception.Message)" + } + } + Finally { + Try { + If ($View) { + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Close' -ArgumentList @() + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($View) } Catch { } + } + ElseIf($SummaryInformation) { + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($SummaryInformation) } Catch { } + } + } + Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($DataBase) } Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($Installer) } Catch { } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-MsiProperty +Function Set-MsiProperty { +<# +.SYNOPSIS + Set a property in the MSI property table. +.DESCRIPTION + Set a property in the MSI property table. +.PARAMETER DataBase + Specify a ComObject representing an MSI database opened in view/modify/update mode. +.PARAMETER PropertyName + The name of the property to be set/modified. +.PARAMETER PropertyValue + The value of the property to be set/modified. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Set-MsiProperty -DataBase $TempMsiPathDatabase -PropertyName 'ALLUSERS' -PropertyValue '1' +.NOTES + This is an internal script function and should typically not be called directly. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [__comobject]$DataBase, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$PropertyName, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$PropertyValue, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Set the MSI Property Name [$PropertyName] with Property Value [$PropertyValue]." -Source ${CmdletName} + + ## Open the requested table view from the database + [__comobject]$View = Invoke-ObjectMethod -InputObject $DataBase -MethodName 'OpenView' -ArgumentList @("SELECT * FROM Property WHERE Property='$PropertyName'") + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Execute' + + ## Retrieve the requested property from the requested table. + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa371136(v=vs.85).aspx + [__comobject]$Record = Invoke-ObjectMethod -InputObject $View -MethodName 'Fetch' + + ## Close the previous view on the MSI database + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Close' -ArgumentList @() + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($View) + + ## Set the MSI property + If ($Record) { + # If the property already exists, then create the view for updating the property + [__comobject]$View = Invoke-ObjectMethod -InputObject $DataBase -MethodName 'OpenView' -ArgumentList @("UPDATE Property SET Value='$PropertyValue' WHERE Property='$PropertyName'") + } + Else { + # If property does not exist, then create view for inserting the property + [__comobject]$View = Invoke-ObjectMethod -InputObject $DataBase -MethodName 'OpenView' -ArgumentList @("INSERT INTO Property (Property, Value) VALUES ('$PropertyName','$PropertyValue')") + } + # Execute the view to set the MSI property + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Execute' + } + Catch { + Write-Log -Message "Failed to set the MSI Property Name [$PropertyName] with Property Value [$PropertyValue]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to set the MSI Property Name [$PropertyName] with Property Value [$PropertyValue]: $($_.Exception.Message)" + } + } + Finally { + Try { + If ($View) { + $null = Invoke-ObjectMethod -InputObject $View -MethodName 'Close' -ArgumentList @() + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($View) + } + } + Catch { } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function New-MsiTransform +Function New-MsiTransform { +<# +.SYNOPSIS + Create a transform file for an MSI database. +.DESCRIPTION + Create a transform file for an MSI database and create/modify properties in the Properties table. +.PARAMETER MsiPath + Specify the path to an MSI file. +.PARAMETER ApplyTransformPath + Specify the path to a transform which should be applied to the MSI database before any new properties are created or modified. +.PARAMETER NewTransformPath + Specify the path where the new transform file with the desired properties will be created. If a transform file of the same name already exists, it will be deleted before a new one is created. + Default is: a) If -ApplyTransformPath was specified but not -NewTransformPath, then .new.mst + b) If only -MsiPath was specified, then .mst +.PARAMETER TransformProperties + Hashtable which contains calls to Set-MsiProperty for configuring the desired properties which should be included in new transform file. + Example hashtable: [hashtable]$TransformProperties = @{ 'ALLUSERS' = '1' } +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + [hashtable]$TransformProperties = { + 'ALLUSERS' = '1' + 'AgreeToLicense' = 'Yes' + 'REBOOT' = 'ReallySuppress' + 'RebootYesNo' = 'No' + 'ROOTDRIVE' = 'C:' + } + New-MsiTransform -MsiPath 'C:\Temp\PSADTInstall.msi' -TransformProperties $TransformProperties +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [string]$MsiPath, + [Parameter(Mandatory=$false)] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType 'Leaf' })] + [string]$ApplyTransformPath, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$NewTransformPath, + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [hashtable]$TransformProperties, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Define properties for how the MSI database is opened + [int32]$msiOpenDatabaseModeReadOnly = 0 + [int32]$msiOpenDatabaseModeTransact = 1 + [int32]$msiViewModifyUpdate = 2 + [int32]$msiViewModifyReplace = 4 + [int32]$msiViewModifyDelete = 6 + [int32]$msiTransformErrorNone = 0 + [int32]$msiTransformValidationNone = 0 + [int32]$msiSuppressApplyTransformErrors = 63 + } + Process { + Try { + Write-Log -Message "Create a transform file for MSI [$MsiPath]." -Source ${CmdletName} + + ## Discover the parent folder that the MSI file resides in + [string]$MsiParentFolder = Split-Path -Path $MsiPath -Parent -ErrorAction 'Stop' + + ## Create a temporary file name for storing a second copy of the MSI database + [string]$TempMsiPath = Join-Path -Path $MsiParentFolder -ChildPath ([IO.Path]::GetFileName(([IO.Path]::GetTempFileName()))) -ErrorAction 'Stop' + + ## Create a second copy of the MSI database + Write-Log -Message "Copy MSI database in path [$MsiPath] to destination [$TempMsiPath]." -Source ${CmdletName} + $null = Copy-Item -LiteralPath $MsiPath -Destination $TempMsiPath -Force -ErrorAction 'Stop' + + ## Create a Windows Installer object + [__comobject]$Installer = New-Object -ComObject 'WindowsInstaller.Installer' -ErrorAction 'Stop' + + ## Open both copies of the MSI database + # Open the original MSI database in read only mode + Write-Log -Message "Open the MSI database [$MsiPath] in read only mode." -Source ${CmdletName} + [__comobject]$MsiPathDatabase = Invoke-ObjectMethod -InputObject $Installer -MethodName 'OpenDatabase' -ArgumentList @($MsiPath, $msiOpenDatabaseModeReadOnly) + # Open the temporary copy of the MSI database in view/modify/update mode + Write-Log -Message "Open the MSI database [$TempMsiPath] in view/modify/update mode." -Source ${CmdletName} + [__comobject]$TempMsiPathDatabase = Invoke-ObjectMethod -InputObject $Installer -MethodName 'OpenDatabase' -ArgumentList @($TempMsiPath, $msiViewModifyUpdate) + + ## If a MSI transform file was specified, then apply it to the temporary copy of the MSI database + If ($ApplyTransformPath) { + Write-Log -Message "Apply transform file [$ApplyTransformPath] to MSI database [$TempMsiPath]." -Source ${CmdletName} + $null = Invoke-ObjectMethod -InputObject $TempMsiPathDatabase -MethodName 'ApplyTransform' -ArgumentList @($ApplyTransformPath, $msiSuppressApplyTransformErrors) + } + + ## Determine the path for the new transform file that will be generated + If (-not $NewTransformPath) { + If ($ApplyTransformPath) { + [string]$NewTransformFileName = [IO.Path]::GetFileNameWithoutExtension($ApplyTransformPath) + '.new' + [IO.Path]::GetExtension($ApplyTransformPath) + } + Else { + [string]$NewTransformFileName = [IO.Path]::GetFileNameWithoutExtension($MsiPath) + '.mst' + } + [string]$NewTransformPath = Join-Path -Path $MsiParentFolder -ChildPath $NewTransformFileName -ErrorAction 'Stop' + } + + ## Set the MSI properties in the temporary copy of the MSI database + $TransformProperties.GetEnumerator() | ForEach-Object { Set-MsiProperty -DataBase $TempMsiPathDatabase -PropertyName $_.Key -PropertyValue $_.Value } + + ## Commit the new properties to the temporary copy of the MSI database + $null = Invoke-ObjectMethod -InputObject $TempMsiPathDatabase -MethodName 'Commit' + + ## Reopen the temporary copy of the MSI database in read only mode + # Release the database object for the temporary copy of the MSI database + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($TempMsiPathDatabase) + # Open the temporary copy of the MSI database in read only mode + Write-Log -Message "Re-open the MSI database [$TempMsiPath] in read only mode." -Source ${CmdletName} + [__comobject]$TempMsiPathDatabase = Invoke-ObjectMethod -InputObject $Installer -MethodName 'OpenDatabase' -ArgumentList @($TempMsiPath, $msiOpenDatabaseModeReadOnly) + + ## Delete the new transform file path if it already exists + If (Test-Path -LiteralPath $NewTransformPath -PathType 'Leaf' -ErrorAction 'Stop') { + Write-Log -Message "A transform file of the same name already exists. Deleting transform file [$NewTransformPath]." -Source ${CmdletName} + $null = Remove-Item -LiteralPath $NewTransformPath -Force -ErrorAction 'Stop' + } + + ## Generate the new transform file by taking the difference between the temporary copy of the MSI database and the original MSI database + Write-Log -Message "Generate new transform file [$NewTransformPath]." -Source ${CmdletName} + $null = Invoke-ObjectMethod -InputObject $TempMsiPathDatabase -MethodName 'GenerateTransform' -ArgumentList @($MsiPathDatabase, $NewTransformPath) + $null = Invoke-ObjectMethod -InputObject $TempMsiPathDatabase -MethodName 'CreateTransformSummaryInfo' -ArgumentList @($MsiPathDatabase, $NewTransformPath, $msiTransformErrorNone, $msiTransformValidationNone) + + If (Test-Path -LiteralPath $NewTransformPath -PathType 'Leaf' -ErrorAction 'Stop') { + Write-Log -Message "Successfully created new transform file in path [$NewTransformPath]." -Source ${CmdletName} + } + Else { + Throw "Failed to generate transform file in path [$NewTransformPath]." + } + } + Catch { + Write-Log -Message "Failed to create new transform file in path [$NewTransformPath]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to create new transform file in path [$NewTransformPath]: $($_.Exception.Message)" + } + } + Finally { + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($TempMsiPathDatabase) } Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($MsiPathDatabase) } Catch { } + Try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($Installer) } Catch { } + Try { + ## Delete the temporary copy of the MSI database + If (Test-Path -LiteralPath $TempMsiPath -PathType 'Leaf' -ErrorAction 'Stop') { + $null = Remove-Item -LiteralPath $TempMsiPath -Force -ErrorAction 'Stop' + } + } + Catch { } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-MSUpdates +Function Test-MSUpdates { +<# +.SYNOPSIS + Test whether a Microsoft Windows update is installed. +.DESCRIPTION + Test whether a Microsoft Windows update is installed. +.PARAMETER KBNumber + KBNumber of the update. +.PARAMETER ContinueOnError + Suppress writing log message to console on failure to write message to log file. Default is: $true. +.EXAMPLE + Test-MSUpdates -KBNumber 'KB2549864' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,Position=0,HelpMessage='Enter the KB Number for the Microsoft Update')] + [ValidateNotNullorEmpty()] + [string]$KBNumber, + [Parameter(Mandatory=$false,Position=1)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Check if Microsoft Update [$kbNumber] is installed." -Source ${CmdletName} + + ## Default is not found + [boolean]$kbFound = $false + + ## Check for update using built in PS cmdlet which uses WMI in the background to gather details + Get-Hotfix -Id $kbNumber -ErrorAction 'SilentlyContinue' | ForEach-Object { $kbFound = $true } + + If (-not $kbFound) { + Write-Log -Message 'Unable to detect Windows update history via Get-Hotfix cmdlet. Trying via COM object.' -Source ${CmdletName} + + ## Check for update using ComObject method (to catch Office updates) + [__comobject]$UpdateSession = New-Object -ComObject "Microsoft.Update.Session" + [__comobject]$UpdateSearcher = $UpdateSession.CreateUpdateSearcher() + # Indicates whether the search results include updates that are superseded by other updates in the search results + $UpdateSearcher.IncludePotentiallySupersededUpdates = $false + # Indicates whether the UpdateSearcher goes online to search for updates. + $UpdateSearcher.Online = $false + [int32]$UpdateHistoryCount = $UpdateSearcher.GetTotalHistoryCount() + If ($UpdateHistoryCount -gt 0) { + [psobject]$UpdateHistory = $UpdateSearcher.QueryHistory(0, $UpdateHistoryCount) | + Select-Object -Property 'Title','Date', + @{Name = 'Operation'; Expression = { Switch ($_.Operation) { 1 {'Installation'}; 2 {'Uninstallation'}; 3 {'Other'} } } }, + @{Name = 'Status'; Expression = { Switch ($_.ResultCode) { 0 {'Not Started'}; 1 {'In Progress'}; 2 {'Successful'}; 3 {'Incomplete'}; 4 {'Failed'}; 5 {'Aborted'} } } }, + 'Description' | + Sort-Object -Property 'Date' -Descending + ForEach ($Update in $UpdateHistory) { + If (($Update.Operation -ne 'Other') -and ($Update.Title -match "\($KBNumber\)")) { + $LatestUpdateHistory = $Update + Break + } + } + If (($LatestUpdateHistory.Operation -eq 'Installation') -and ($LatestUpdateHistory.Status -eq 'Successful')) { + Write-Log -Message "Discovered the following Microsoft Update: `n$($LatestUpdateHistory | Format-List | Out-String)" -Source ${CmdletName} + $kbFound = $true + } + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateSession) + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($UpdateSearcher) + } + Else { + Write-Log -Message 'Unable to detect Windows update history via COM object.' -Source ${CmdletName} + } + } + + ## Return Result + If (-not $kbFound) { + Write-Log -Message "Microsoft Update [$kbNumber] is not installed." -Source ${CmdletName} + Write-Output -InputObject $false + } + Else { + Write-Log -Message "Microsoft Update [$kbNumber] is installed." -Source ${CmdletName} + Write-Output -InputObject $true + } + } + Catch { + Write-Log -Message "Failed discovering Microsoft Update [$kbNumber]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed discovering Microsoft Update [$kbNumber]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Install-MSUpdates +Function Install-MSUpdates { +<# +.SYNOPSIS + Install all Microsoft Updates in a given directory. +.DESCRIPTION + Install all Microsoft Updates of type ".exe", ".msu", or ".msp" in a given directory (recursively search directory). +.PARAMETER Directory + Directory containing the updates. +.EXAMPLE + Install-MSUpdates -Directory "$dirFiles\MSUpdates" +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$Directory + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Write-Log -Message "Recursively install all Microsoft Updates in directory [$Directory]." -Source ${CmdletName} + + ## KB Number pattern match + $kbPattern = '(?i)kb\d{6,8}' + + ## Get all hotfixes and install if required + [IO.FileInfo[]]$files = Get-ChildItem -LiteralPath $Directory -Recurse -Include ('*.exe','*.msu','*.msp') + ForEach ($file in $files) { + If ($file.Name -match 'redist') { + [version]$redistVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($file.FullName).ProductVersion + [string]$redistDescription = [Diagnostics.FileVersionInfo]::GetVersionInfo($file.FullName).FileDescription + + Write-Log -Message "Install [$redistDescription $redistVersion]..." -Source ${CmdletName} + # Handle older redistributables (ie, VC++ 2005) + If ($redistDescription -match 'Win32 Cabinet Self-Extractor') { + Execute-Process -Path $file.FullName -Parameters '/q' -WindowStyle 'Hidden' -IgnoreExitCodes "*" + } + Else { + Execute-Process -Path $file.FullName -Parameters '/quiet /norestart' -WindowStyle 'Hidden' -IgnoreExitCodes "*" + } + } + Else { + # Get the KB number of the file + [string]$kbNumber = [regex]::Match($file.Name, $kbPattern).ToString() + If (-not $kbNumber) { Continue } + + # Check to see whether the KB is already installed + If (-not (Test-MSUpdates -KBNumber $kbNumber)) { + Write-Log -Message "KB Number [$KBNumber] was not detected and will be installed." -Source ${CmdletName} + Switch ($file.Extension) { + # Installation type for executables (i.e., Microsoft Office Updates) + '.exe' { Execute-Process -Path $file.FullName -Parameters '/quiet /norestart' -WindowStyle 'Hidden' -IgnoreExitCodes "*" } + # Installation type for Windows updates using Windows Update Standalone Installer + '.msu' { Execute-Process -Path $exeWusa -Parameters "`"$($file.FullName)`" /quiet /norestart" -WindowStyle 'Hidden' -IgnoreExitCodes "*" } + # Installation type for Windows Installer Patch + '.msp' { Execute-MSI -Action 'Patch' -Path $file.FullName -IgnoreExitCodes "*" } + } + } + Else { + Write-Log -Message "KB Number [$kbNumber] is already installed. Continue..." -Source ${CmdletName} + } + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-WindowTitle +Function Get-WindowTitle { +<# +.SYNOPSIS + Search for an open window title and return details about the window. +.DESCRIPTION + Search for a window title. If window title searched for returns more than one result, then details for each window will be displayed. + Returns the following properties for each window: WindowTitle, WindowHandle, ParentProcess, ParentProcessMainWindowHandle, ParentProcessId. + Function does not work in SYSTEM context unless launched with "psexec.exe -s -i" to run it as an interactive process under the SYSTEM account. +.PARAMETER WindowTitle + The title of the application window to search for using regex matching. +.PARAMETER GetAllWindowTitles + Get titles for all open windows on the system. +.PARAMETER DisableFunctionLogging + Disables logging messages to the script log file. +.EXAMPLE + Get-WindowTitle -WindowTitle 'Microsoft Word' + Gets details for each window that has the words "Microsoft Word" in the title. +.EXAMPLE + Get-WindowTitle -GetAllWindowTitles + Gets details for all windows with a title. +.EXAMPLE + Get-WindowTitle -GetAllWindowTitles | Where-Object { $_.ParentProcess -eq 'WINWORD' } + Get details for all windows belonging to Microsoft Word process with name "WINWORD". +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,ParameterSetName='SearchWinTitle')] + [AllowEmptyString()] + [string]$WindowTitle, + [Parameter(Mandatory=$true,ParameterSetName='GetAllWinTitles')] + [ValidateNotNullorEmpty()] + [switch]$GetAllWindowTitles = $false, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [switch]$DisableFunctionLogging = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + If ($PSCmdlet.ParameterSetName -eq 'SearchWinTitle') { + If (-not $DisableFunctionLogging) { Write-Log -Message "Find open window title(s) [$WindowTitle] using regex matching." -Source ${CmdletName} } + } + ElseIf ($PSCmdlet.ParameterSetName -eq 'GetAllWinTitles') { + If (-not $DisableFunctionLogging) { Write-Log -Message 'Find all open window title(s).' -Source ${CmdletName} } + } + + ## Get all window handles for visible windows + [IntPtr[]]$VisibleWindowHandles = [PSADT.UiAutomation]::EnumWindows() | Where-Object { [PSADT.UiAutomation]::IsWindowVisible($_) } + + ## Discover details about each visible window that was discovered + ForEach ($VisibleWindowHandle in $VisibleWindowHandles) { + If (-not $VisibleWindowHandle) { Continue } + ## Get the window title + [string]$VisibleWindowTitle = [PSADT.UiAutomation]::GetWindowText($VisibleWindowHandle) + If ($VisibleWindowTitle) { + ## Get the process that spawned the window + [Diagnostics.Process]$Process = Get-Process -ErrorAction 'Stop' | Where-Object { $_.Id -eq [PSADT.UiAutomation]::GetWindowThreadProcessId($VisibleWindowHandle) } + If ($Process) { + ## Build custom object with details about the window and the process + [psobject]$VisibleWindow = New-Object -TypeName 'PSObject' -Property @{ + WindowTitle = $VisibleWindowTitle + WindowHandle = $VisibleWindowHandle + ParentProcess= $Process.Name + ParentProcessMainWindowHandle = $Process.MainWindowHandle + ParentProcessId = $Process.Id + } + + ## Only save/return the window and process details which match the search criteria + If ($PSCmdlet.ParameterSetName -eq 'SearchWinTitle') { + $MatchResult = $VisibleWindow.WindowTitle -match $WindowTitle + If ($MatchResult) { + [psobject[]]$VisibleWindows += $VisibleWindow + } + } + ElseIf ($PSCmdlet.ParameterSetName -eq 'GetAllWinTitles') { + [psobject[]]$VisibleWindows += $VisibleWindow + } + } + } + } + } + Catch { + If (-not $DisableFunctionLogging) { Write-Log -Message "Failed to get requested window title(s). `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} } + } + } + End { + Write-Output -InputObject $VisibleWindows + + If ($DisableFunctionLogging) { . $RevertScriptLogging } + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Send-Keys +Function Send-Keys { +<# +.SYNOPSIS + Send a sequence of keys to one or more application windows. +.DESCRIPTION + Send a sequence of keys to one or more application window. If window title searched for returns more than one window, then all of them will receive the sent keys. + Function does not work in SYSTEM context unless launched with "psexec.exe -s -i" to run it as an interactive process under the SYSTEM account. +.PARAMETER WindowTitle + The title of the application window to search for using regex matching. +.PARAMETER GetAllWindowTitles + Get titles for all open windows on the system. +.PARAMETER WindowHandle + Send keys to a specific window where the Window Handle is already known. +.PARAMETER Keys + The sequence of keys to send. Info on Key input at: http://msdn.microsoft.com/en-us/library/System.Windows.Forms.SendKeys(v=vs.100).aspx +.PARAMETER WaitSeconds + An optional number of seconds to wait after the sending of the keys. +.EXAMPLE + Send-Keys -WindowTitle 'foobar - Notepad' -Key 'Hello world' + Send the sequence of keys "Hello world" to the application titled "foobar - Notepad". +.EXAMPLE + Send-Keys -WindowTitle 'foobar - Notepad' -Key 'Hello world' -WaitSeconds 5 + Send the sequence of keys "Hello world" to the application titled "foobar - Notepad" and wait 5 seconds. +.EXAMPLE + Send-Keys -WindowHandle ([IntPtr]17368294) -Key 'Hello world' + Send the sequence of keys "Hello world" to the application with a Window Handle of '17368294'. +.NOTES +.LINK + http://msdn.microsoft.com/en-us/library/System.Windows.Forms.SendKeys(v=vs.100).aspx + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false,Position=0)] + [AllowEmptyString()] + [ValidateNotNull()] + [string]$WindowTitle, + [Parameter(Mandatory=$false,Position=1)] + [ValidateNotNullorEmpty()] + [switch]$GetAllWindowTitles = $false, + [Parameter(Mandatory=$false,Position=2)] + [ValidateNotNullorEmpty()] + [IntPtr]$WindowHandle, + [Parameter(Mandatory=$false,Position=3)] + [ValidateNotNullorEmpty()] + [string]$Keys, + [Parameter(Mandatory=$false,Position=4)] + [ValidateNotNullorEmpty()] + [int32]$WaitSeconds + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Load assembly containing class System.Windows.Forms.SendKeys + Add-Type -AssemblyName 'System.Windows.Forms' -ErrorAction 'Stop' + + [scriptblock]$SendKeys = { + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [IntPtr]$WindowHandle + ) + Try { + ## Bring the window to the foreground + [boolean]$IsBringWindowToFrontSuccess = [PSADT.UiAutomation]::BringWindowToFront($WindowHandle) + If (-not $IsBringWindowToFrontSuccess) { Throw 'Failed to bring window to foreground.'} + + ## Send the Key sequence + If ($Keys) { + [boolean]$IsWindowModal = If ([PSADT.UiAutomation]::IsWindowEnabled($WindowHandle)) { $false } Else { $true } + If ($IsWindowModal) { Throw 'Unable to send keys to window because it may be disabled due to a modal dialog being shown.' } + [Windows.Forms.SendKeys]::SendWait($Keys) + Write-Log -Message "Sent key(s) [$Keys] to window title [$($Window.WindowTitle)] with window handle [$WindowHandle]." -Source ${CmdletName} + + If ($WaitSeconds) { + Write-Log -Message "Sleeping for [$WaitSeconds] seconds." -Source ${CmdletName} + Start-Sleep -Seconds $WaitSeconds + } + } + } + Catch { + Write-Log -Message "Failed to send keys to window title [$($Window.WindowTitle)] with window handle [$WindowHandle]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + } + Process { + Try { + If ($WindowHandle) { + [psobject]$Window = Get-WindowTitle -GetAllWindowTitles | Where-Object { $_.WindowHandle -eq $WindowHandle } + If (-not $Window) { + Write-Log -Message "No windows with Window Handle [$WindowHandle] were discovered." -Severity 2 -Source ${CmdletName} + Return + } + & $SendKeys -WindowHandle $Window.WindowHandle + } + Else { + [hashtable]$GetWindowTitleSplat = @{} + If ($GetAllWindowTitles) { $GetWindowTitleSplat.Add( 'GetAllWindowTitles', $GetAllWindowTitles) } + Else { $GetWindowTitleSplat.Add( 'WindowTitle', $WindowTitle) } + [psobject[]]$AllWindows = Get-WindowTitle @GetWindowTitleSplat + If (-not $AllWindows) { + Write-Log -Message 'No windows with the specified details were discovered.' -Severity 2 -Source ${CmdletName} + Return + } + + ForEach ($Window in $AllWindows) { + & $SendKeys -WindowHandle $Window.WindowHandle + } + } + } + Catch { + Write-Log -Message "Failed to send keys to specified window. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-Battery +Function Test-Battery { +<# +.SYNOPSIS + Tests whether the local machine is running on AC power or not. +.DESCRIPTION + Tests whether the local machine is running on AC power and returns true/false. For detailed information, use -PassThru option. +.PARAMETER PassThru + Outputs a hashtable containing the following properties: + IsLaptop, IsUsingACPower, ACPowerLineStatus, BatteryChargeStatus, BatteryLifePercent, BatteryLifeRemaining, BatteryFullLifetime +.EXAMPLE + Test-Battery +.EXAMPLE + (Test-Battery -PassThru).IsLaptop + Determines if the current system is a laptop or not. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$PassThru = $false + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## PowerStatus class found in this assembly is more reliable than WMI in cases where the battery is failing. + Add-Type -Assembly 'System.Windows.Forms' -ErrorAction 'SilentlyContinue' + + ## Initialize a hashtable to store information about system type and power status + [hashtable]$SystemTypePowerStatus = @{ } + } + Process { + Write-Log -Message 'Check if system is using AC power or if it is running on battery...' -Source ${CmdletName} + + [Windows.Forms.PowerStatus]$PowerStatus = [Windows.Forms.SystemInformation]::PowerStatus + + ## Get the system power status. Indicates whether the system is using AC power or if the status is unknown. Possible values: + # Offline : The system is not using AC power. + # Online : The system is using AC power. + # Unknown : The power status of the system is unknown. + [string]$PowerLineStatus = $PowerStatus.PowerLineStatus + $SystemTypePowerStatus.Add('ACPowerLineStatus', $PowerStatus.PowerLineStatus) + + ## Get the current battery charge status. Possible values: High, Low, Critical, Charging, NoSystemBattery, Unknown. + [string]$BatteryChargeStatus = $PowerStatus.BatteryChargeStatus + $SystemTypePowerStatus.Add('BatteryChargeStatus', $PowerStatus.BatteryChargeStatus) + + ## Get the approximate amount, from 0.00 to 1.0, of full battery charge remaining. + # This property can report 1.0 when the battery is damaged and Windows can't detect a battery. + # Therefore, this property is only indicative of battery charge remaining if 'BatteryChargeStatus' property is not reporting 'NoSystemBattery' or 'Unknown'. + [single]$BatteryLifePercent = $PowerStatus.BatteryLifePercent + If (($BatteryChargeStatus -eq 'NoSystemBattery') -or ($BatteryChargeStatus -eq 'Unknown')) { + [single]$BatteryLifePercent = 0.0 + } + $SystemTypePowerStatus.Add('BatteryLifePercent', $PowerStatus.BatteryLifePercent) + + ## The reported approximate number of seconds of battery life remaining. It will report -1 if the remaining life is unknown because the system is on AC power. + [int32]$BatteryLifeRemaining = $PowerStatus.BatteryLifeRemaining + $SystemTypePowerStatus.Add('BatteryLifeRemaining', $PowerStatus.BatteryLifeRemaining) + + ## Get the manufacturer reported full charge lifetime of the primary battery power source in seconds. + # The reported number of seconds of battery life available when the battery is fully charged, or -1 if it is unknown. + # This will only be reported if the battery supports reporting this information. You will most likely get -1, indicating unknown. + [int32]$BatteryFullLifetime = $PowerStatus.BatteryFullLifetime + $SystemTypePowerStatus.Add('BatteryFullLifetime', $PowerStatus.BatteryFullLifetime) + + ## Determine if the system is using AC power + [boolean]$OnACPower = $false + If ($PowerLineStatus -eq 'Online') { + Write-Log -Message 'System is using AC power.' -Source ${CmdletName} + $OnACPower = $true + } + ElseIf ($PowerLineStatus -eq 'Offline') { + Write-Log -Message 'System is using battery power.' -Source ${CmdletName} + } + ElseIf ($PowerLineStatus -eq 'Unknown') { + If (($BatteryChargeStatus -eq 'NoSystemBattery') -or ($BatteryChargeStatus -eq 'Unknown')) { + Write-Log -Message "System power status is [$PowerLineStatus] and battery charge status is [$BatteryChargeStatus]. This is most likely due to a damaged battery so we will report system is using AC power." -Source ${CmdletName} + $OnACPower = $true + } + Else { + Write-Log -Message "System power status is [$PowerLineStatus] and battery charge status is [$BatteryChargeStatus]. Therefore, we will report system is using battery power." -Source ${CmdletName} + } + } + $SystemTypePowerStatus.Add('IsUsingACPower', $OnACPower) + + ## Determine if the system is a laptop + [boolean]$IsLaptop = $false + If (($BatteryChargeStatus -eq 'NoSystemBattery') -or ($BatteryChargeStatus -eq 'Unknown')) { + $IsLaptop = $false + } + Else { + $IsLaptop = $true + } + # Chassis Types + [int32[]]$ChassisTypes = Get-WmiObject -Class 'Win32_SystemEnclosure' | Where-Object { $_.ChassisTypes } | Select-Object -ExpandProperty 'ChassisTypes' + Write-Log -Message "The following system chassis types were detected [$($ChassisTypes -join ',')]." -Source ${CmdletName} + ForEach ($ChassisType in $ChassisTypes) { + Switch ($ChassisType) { + { $_ -eq 9 -or $_ -eq 10 -or $_ -eq 14 } { $IsLaptop = $true } # 9=Laptop, 10=Notebook, 14=Sub Notebook + { $_ -eq 3 } { $IsLaptop = $false } # 3=Desktop + } + } + # Add IsLaptop property to hashtable + $SystemTypePowerStatus.Add('IsLaptop', $IsLaptop) + + If ($PassThru) { + Write-Output -InputObject $SystemTypePowerStatus + } + Else { + Write-Output -InputObject $OnACPower + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-NetworkConnection +Function Test-NetworkConnection { +<# +.SYNOPSIS + Tests for an active local network connection, excluding wireless and virtual network adapters. +.DESCRIPTION + Tests for an active local network connection, excluding wireless and virtual network adapters, by querying the Win32_NetworkAdapter WMI class. +.EXAMPLE + Test-NetworkConnection +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Write-Log -Message 'Check if system is using a wired network connection...' -Source ${CmdletName} + + [psobject[]]$networkConnected = Get-WmiObject -Class 'Win32_NetworkAdapter' | Where-Object { ($_.NetConnectionStatus -eq 2) -and ($_.NetConnectionID -match 'Local' -or $_.NetConnectionID -match 'Ethernet') -and ($_.NetConnectionID -notmatch 'Wireless') -and ($_.Name -notmatch 'Virtual') } -ErrorAction 'SilentlyContinue' + [boolean]$onNetwork = $false + If ($networkConnected) { + Write-Log -Message 'Wired network connection found.' -Source ${CmdletName} + [boolean]$onNetwork = $true + } + Else { + Write-Log -Message 'Wired network connection not found.' -Source ${CmdletName} + } + + Write-Output -InputObject $onNetwork + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-PowerPoint +Function Test-PowerPoint { +<# +.SYNOPSIS + Tests whether PowerPoint is running in either fullscreen slideshow mode or presentation mode. +.DESCRIPTION + Tests whether someone is presenting using PowerPoint in either fullscreen slideshow mode or presentation mode. +.EXAMPLE + Test-PowerPoint +.NOTES + This function can only execute detection logic if the process is in interactive mode. + There is a possiblity of a false positive if the PowerPoint filename starts with "PowerPoint Slide Show". +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Check if PowerPoint is in either fullscreen slideshow mode or presentation mode...' -Source ${CmdletName} + Try { + [Diagnostics.Process[]]$PowerPointProcess = Get-Process -ErrorAction 'Stop' | Where-Object { $_.ProcessName -eq 'POWERPNT' } + If ($PowerPointProcess) { + [boolean]$IsPowerPointRunning = $true + Write-Log -Message 'PowerPoint application is running.' -Source ${CmdletName} + } + Else { + [boolean]$IsPowerPointRunning = $false + Write-Log -Message 'PowerPoint application is not running.' -Source ${CmdletName} + } + } + Catch { + Throw + } + + [nullable[boolean]]$IsPowerPointFullScreen = $false + If ($IsPowerPointRunning) { + ## Detect if PowerPoint is in fullscreen mode or Presentation Mode, detection method only works if process is interactive + If ([Environment]::UserInteractive) { + # Check if "POWERPNT" process has a window with a title that begins with "PowerPoint Slide Show" or "Powerpoint-" for non-English language systems. + # There is a possiblity of a false positive if the PowerPoint filename starts with "PowerPoint Slide Show" + [psobject]$PowerPointWindow = Get-WindowTitle -GetAllWindowTitles | Where-Object { $_.WindowTitle -match '^PowerPoint Slide Show' -or $_.WindowTitle -match '^PowerPoint-' } | Where-Object { $_.ParentProcess -eq 'POWERPNT'} | Select-Object -First 1 + If ($PowerPointWindow) { + [nullable[boolean]]$IsPowerPointFullScreen = $true + Write-Log -Message 'Detected that PowerPoint process [POWERPNT] has a window with a title that beings with [PowerPoint Slide Show] or [PowerPoint-].' -Source ${CmdletName} + } + Else { + Write-Log -Message 'Detected that PowerPoint process [POWERPNT] does not have a window with a title that beings with [PowerPoint Slide Show] or [PowerPoint-].' -Source ${CmdletName} + Try { + [int32[]]$PowerPointProcessIDs = $PowerPointProcess | Select-Object -ExpandProperty 'Id' -ErrorAction 'Stop' + Write-Log -Message "PowerPoint process [POWERPNT] has process id(s) [$($PowerPointProcessIDs -join ', ')]." -Source ${CmdletName} + } + Catch { + Write-Log -Message "Unable to retrieve process id(s) for [POWERPNT] process. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + } + } + + ## If previous detection method did not detect PowerPoint in fullscreen mode, then check if PowerPoint is in Presentation Mode (check only works on Windows Vista or higher) + If ((-not $IsPowerPointFullScreen) -and (([version]$envOSVersion).Major -gt 5)) { + # Note: below method does not detect PowerPoint presentation mode if the presentation is on a monitor that does not have current mouse input control + [string]$UserNotificationState = [PSADT.UiAutomation]::GetUserNotificationState() + Write-Log -Message "Detected user notification state [$UserNotificationState]." -Source ${CmdletName} + Switch ($UserNotificationState) { + 'PresentationMode' { + Write-Log -Message "Detected that system is in [Presentation Mode]." -Source ${CmdletName} + [nullable[boolean]]$IsPowerPointFullScreen = $true + } + 'FullScreenOrPresentationModeOrLoginScreen' { + If (([string]$PowerPointProcessIDs) -and ($PowerPointProcessIDs -contains [PSADT.UIAutomation]::GetWindowThreadProcessID([PSADT.UIAutomation]::GetForeGroundWindow()))) { + Write-Log -Message "Detected that fullscreen foreground window matches PowerPoint process id." -Source ${CmdletName} + [nullable[boolean]]$IsPowerPointFullScreen = $true + } + } + } + } + } + Else { + [nullable[boolean]]$IsPowerPointFullScreen = $null + Write-Log -Message 'Unable to run check to see if PowerPoint is in fullscreen mode or Presentation Mode because current process is not interactive. Configure script to run in interactive mode in your deployment tool. If using SCCM Application Model, then make sure "Allow users to view and interact with the program installation" is selected. If using SCCM Package Model, then make sure "Allow users to interact with this program" is selected.' -Severity 2 -Source ${CmdletName} + } + } + } + Catch { + [nullable[boolean]]$IsPowerPointFullScreen = $null + Write-Log -Message "Failed check to see if PowerPoint is running in fullscreen slideshow mode. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-Log -Message "PowerPoint is running in fullscreen mode [$IsPowerPointFullScreen]." -Source ${CmdletName} + Write-Output -InputObject $IsPowerPointFullScreen + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Invoke-SCCMTask +Function Invoke-SCCMTask { +<# +.SYNOPSIS + Triggers SCCM to invoke the requested schedule task id. +.DESCRIPTION + Triggers SCCM to invoke the requested schedule task id. +.PARAMETER ScheduleId + Name of the schedule id to trigger. + Options: HardwareInventory, SoftwareInventory, HeartbeatDiscovery, SoftwareInventoryFileCollection, RequestMachinePolicy, EvaluateMachinePolicy, + LocationServicesCleanup, SoftwareMeteringReport, SourceUpdate, PolicyAgentCleanup, RequestMachinePolicy2, CertificateMaintenance, PeerDistributionPointStatus, + PeerDistributionPointProvisioning, ComplianceIntervalEnforcement, SoftwareUpdatesAgentAssignmentEvaluation, UploadStateMessage, StateMessageManager, + SoftwareUpdatesScan, AMTProvisionCycle, UpdateStorePolicy, StateSystemBulkSend, ApplicationManagerPolicyAction, PowerManagementStartSummarizer +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Invoke-SCCMTask 'SoftwareUpdatesScan' +.EXAMPLE + Invoke-SCCMTask +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateSet('HardwareInventory','SoftwareInventory','HeartbeatDiscovery','SoftwareInventoryFileCollection','RequestMachinePolicy','EvaluateMachinePolicy','LocationServicesCleanup','SoftwareMeteringReport','SourceUpdate','PolicyAgentCleanup','RequestMachinePolicy2','CertificateMaintenance','PeerDistributionPointStatus','PeerDistributionPointProvisioning','ComplianceIntervalEnforcement','SoftwareUpdatesAgentAssignmentEvaluation','UploadStateMessage','StateMessageManager','SoftwareUpdatesScan','AMTProvisionCycle','UpdateStorePolicy','StateSystemBulkSend','ApplicationManagerPolicyAction','PowerManagementStartSummarizer')] + [string]$ScheduleID, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Invoke SCCM Schedule Task ID [$ScheduleId]..." -Source ${CmdletName} + + ## Make sure SCCM client is installed and running + Write-Log -Message 'Check to see if SCCM Client service [ccmexec] is installed and running.' -Source ${CmdletName} + If (Test-ServiceExists -Name 'ccmexec') { + If ($(Get-Service -Name 'ccmexec' -ErrorAction 'SilentlyContinue').Status -ne 'Running') { + Throw "SCCM Client Service [ccmexec] exists but it is not in a 'Running' state." + } + } Else { + Throw 'SCCM Client Service [ccmexec] does not exist. The SCCM Client may not be installed.' + } + + ## Determine the SCCM Client Version + Try { + [version]$SCCMClientVersion = Get-WmiObject -Namespace 'ROOT\CCM' -Class 'CCM_InstalledComponent' -ErrorAction 'Stop' | Where-Object { $_.Name -eq 'SmsClient' } | Select-Object -ExpandProperty 'Version' -ErrorAction 'Stop' + Write-Log -Message "Installed SCCM Client Version Number [$SCCMClientVersion]." -Source ${CmdletName} + } + Catch { + Write-Log -Message "Failed to determine the SCCM client version number. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + Throw 'Failed to determine the SCCM client version number.' + } + + ## Create a hashtable of Schedule IDs compatible with SCCM Client 2007 + [hashtable]$ScheduleIds = @{ + HardwareInventory = '{00000000-0000-0000-0000-000000000001}'; # Hardware Inventory Collection Task + SoftwareInventory = '{00000000-0000-0000-0000-000000000002}'; # Software Inventory Collection Task + HeartbeatDiscovery = '{00000000-0000-0000-0000-000000000003}'; # Heartbeat Discovery Cycle + SoftwareInventoryFileCollection = '{00000000-0000-0000-0000-000000000010}'; # Software Inventory File Collection Task + RequestMachinePolicy = '{00000000-0000-0000-0000-000000000021}'; # Request Machine Policy Assignments + EvaluateMachinePolicy = '{00000000-0000-0000-0000-000000000022}'; # Evaluate Machine Policy Assignments + RefreshDefaultMp = '{00000000-0000-0000-0000-000000000023}'; # Refresh Default MP Task + RefreshLocationServices = '{00000000-0000-0000-0000-000000000024}'; # Refresh Location Services Task + LocationServicesCleanup = '{00000000-0000-0000-0000-000000000025}'; # Location Services Cleanup Task + SoftwareMeteringReport = '{00000000-0000-0000-0000-000000000031}'; # Software Metering Report Cycle + SourceUpdate = '{00000000-0000-0000-0000-000000000032}'; # Source Update Manage Update Cycle + PolicyAgentCleanup = '{00000000-0000-0000-0000-000000000040}'; # Policy Agent Cleanup Cycle + RequestMachinePolicy2 = '{00000000-0000-0000-0000-000000000042}'; # Request Machine Policy Assignments + CertificateMaintenance = '{00000000-0000-0000-0000-000000000051}'; # Certificate Maintenance Cycle + PeerDistributionPointStatus = '{00000000-0000-0000-0000-000000000061}'; # Peer Distribution Point Status Task + PeerDistributionPointProvisioning = '{00000000-0000-0000-0000-000000000062}'; # Peer Distribution Point Provisioning Status Task + ComplianceIntervalEnforcement = '{00000000-0000-0000-0000-000000000071}'; # Compliance Interval Enforcement + SoftwareUpdatesAgentAssignmentEvaluation = '{00000000-0000-0000-0000-000000000108}'; # Software Updates Agent Assignment Evaluation Cycle + UploadStateMessage = '{00000000-0000-0000-0000-000000000111}'; # Send Unsent State Messages + StateMessageManager = '{00000000-0000-0000-0000-000000000112}'; # State Message Manager Task + SoftwareUpdatesScan = '{00000000-0000-0000-0000-000000000113}'; # Force Update Scan + AMTProvisionCycle = '{00000000-0000-0000-0000-000000000120}'; # AMT Provision Cycle + } + + ## If SCCM 2012 Client or higher, modify hashtabe containing Schedule IDs so that it only has the ones compatible with this version of the SCCM client + If ($SCCMClientVersion.Major -ge 5) { + $ScheduleIds.Remove('PeerDistributionPointStatus') + $ScheduleIds.Remove('PeerDistributionPointProvisioning') + $ScheduleIds.Remove('ComplianceIntervalEnforcement') + $ScheduleIds.Add('UpdateStorePolicy','{00000000-0000-0000-0000-000000000114}') # Update Store Policy + $ScheduleIds.Add('StateSystemBulkSend','{00000000-0000-0000-0000-000000000116}') # State System Policy Bulk Send Low + $ScheduleIds.Add('ApplicationManagerPolicyAction','{00000000-0000-0000-0000-000000000121}') # Application Manager Policy Action + $ScheduleIds.Add('PowerManagementStartSummarizer','{00000000-0000-0000-0000-000000000131}') # Power Management Start Summarizer + } + + ## Determine if the requested Schedule ID is available on this version of the SCCM Client + If (-not ($ScheduleIds.ContainsKey($ScheduleId))) { + Throw "The requested ScheduleId [$ScheduleId] is not available with this version of the SCCM Client [$SCCMClientVersion]." + } + + ## Trigger SCCM task + Write-Log -Message "Trigger SCCM Task ID [$ScheduleId]." -Source ${CmdletName} + [Management.ManagementClass]$SmsClient = [WMIClass]'ROOT\CCM:SMS_Client' + $null = $SmsClient.TriggerSchedule($ScheduleIds.$ScheduleID) + } + Catch { + Write-Log -Message "Failed to trigger SCCM Schedule Task ID [$($ScheduleIds.$ScheduleId)]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to trigger SCCM Schedule Task ID [$($ScheduleIds.$ScheduleId)]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Install-SCCMSoftwareUpdates +Function Install-SCCMSoftwareUpdates { +<# +.SYNOPSIS + Scans for outstanding SCCM updates to be installed and installs the pending updates. +.DESCRIPTION + Scans for outstanding SCCM updates to be installed and installs the pending updates. + Only compatible with SCCM 2012 Client or higher. This function can take several minutes to run. +.PARAMETER SoftwareUpdatesScanWaitInSeconds + The amount of time to wait in seconds for the software updates scan to complete. Default is: 180 seconds. +.PARAMETER WaitForPendingUpdatesTimeout + The amount of time to wait for missing and pending updates to install before exiting the function. Default is: 45 minutes. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Install-SCCMSoftwareUpdates +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [int32]$SoftwareUpdatesScanWaitInSeconds = 180, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [timespan]$WaitForPendingUpdatesTimeout = $(New-TimeSpan -Minutes 45), + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Scan for and install pending SCCM software updates.' -Source ${CmdletName} + + ## Make sure SCCM client is installed and running + Write-Log -Message 'Check to see if SCCM Client service [ccmexec] is installed and running.' -Source ${CmdletName} + If (Test-ServiceExists -Name 'ccmexec') { + If ($(Get-Service -Name 'ccmexec' -ErrorAction 'SilentlyContinue').Status -ne 'Running') { + Throw "SCCM Client Service [ccmexec] exists but it is not in a 'Running' state." + } + } Else { + Throw 'SCCM Client Service [ccmexec] does not exist. The SCCM Client may not be installed.' + } + + ## Determine the SCCM Client Version + Try { + [version]$SCCMClientVersion = Get-WmiObject -Namespace 'ROOT\CCM' -Class 'CCM_InstalledComponent' -ErrorAction 'Stop' | Where-Object { $_.Name -eq 'SmsClient' } | Select-Object -ExpandProperty 'Version' -ErrorAction 'Stop' + Write-Log -Message "Installed SCCM Client Version Number [$SCCMClientVersion]." -Source ${CmdletName} + } + Catch { + Write-Log -Message "Failed to determine the SCCM client version number. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + Throw 'Failed to determine the SCCM client version number.' + } + # If SCCM 2007 Client or lower, exit function + If ($SCCMClientVersion.Major -le 4) { + Throw 'SCCM 2007 or lower, which is incompatible with this function, was detected on this system.' + } + + $StartTime = Get-Date + ## Trigger SCCM client scan for Software Updates + Write-Log -Message 'Trigger SCCM client scan for Software Updates...' -Source ${CmdletName} + Invoke-SCCMTask -ScheduleId 'SoftwareUpdatesScan' + + Write-Log -Message "The SCCM client scan for Software Updates has been triggered. The script is suspended for [$SoftwareUpdatesScanWaitInSeconds] seconds to let the update scan finish." -Source ${CmdletName} + Start-Sleep -Seconds $SoftwareUpdatesScanWaitInSeconds + + ## Find the number of missing updates + Try { + [Management.ManagementObject[]]$CMMissingUpdates = @(Get-WmiObject -Namespace 'ROOT\CCM\ClientSDK' -Query "SELECT * FROM CCM_SoftwareUpdate WHERE ComplianceState = '0'" -ErrorAction 'Stop') + } + Catch { + Write-Log -Message "Failed to find the number of missing software updates. `n$(Resolve-Error)" -Severity 2 -Source ${CmdletName} + Throw 'Failed to find the number of missing software updates.' + } + + ## Install missing updates and wait for pending updates to finish installing + If ($CMMissingUpdates.Count) { + # Install missing updates + Write-Log -Message "Install missing updates. The number of missing updates is [$($CMMissingUpdates.Count)]." -Source ${CmdletName} + $CMInstallMissingUpdates = (Get-WmiObject -Namespace 'ROOT\CCM\ClientSDK' -Class 'CCM_SoftwareUpdatesManager' -List).InstallUpdates($CMMissingUpdates) + + # Wait for pending updates to finish installing or the timeout value to expire + Do { + Start-Sleep -Seconds 60 + [array]$CMInstallPendingUpdates = @(Get-WmiObject -Namespace "ROOT\CCM\ClientSDK" -Query "SELECT * FROM CCM_SoftwareUpdate WHERE EvaluationState = 6 or EvaluationState = 7") + Write-Log -Message "The number of updates pending installation is [$($CMInstallPendingUpdates.Count)]." -Source ${CmdletName} + } While (($CMInstallPendingUpdates.Count -ne 0) -and ((New-TimeSpan -Start $StartTime -End $(Get-Date)) -lt $WaitForPendingUpdatesTimeout)) + } + Else { + Write-Log -Message 'There are no missing updates.' -Source ${CmdletName} + } + } + Catch { + Write-Log -Message "Failed to trigger installation of missing software updates. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to trigger installation of missing software updates: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Update-GroupPolicy +Function Update-GroupPolicy { +<# +.SYNOPSIS + Performs a gpupdate command to refresh Group Policies on the local machine. +.DESCRIPTION + Performs a gpupdate command to refresh Group Policies on the local machine. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Update-GroupPolicy +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + [string[]]$GPUpdateCmds = '/C echo N | gpupdate.exe /Target:Computer /Force', '/C echo N | gpupdate.exe /Target:User /Force' + [int32]$InstallCount = 0 + ForEach ($GPUpdateCmd in $GPUpdateCmds) { + Try { + If ($InstallCount -eq 0) { + [string]$InstallMsg = 'Update Group Policies for the Machine' + Write-Log -Message "$($InstallMsg)..." -Source ${CmdletName} + } + Else { + [string]$InstallMsg = 'Update Group Policies for the User' + Write-Log -Message "$($InstallMsg)..." -Source ${CmdletName} + } + [psobject]$ExecuteResult = Execute-Process -Path "$envWinDir\system32\cmd.exe" -Parameters $GPUpdateCmd -WindowStyle 'Hidden' -PassThru -ExitOnProcessFailure $false + + If ($ExecuteResult.ExitCode -ne 0) { + If ($ExecuteResult.ExitCode -eq 60002) { + Throw "Execute-Process function failed with exit code [$($ExecuteResult.ExitCode)]." + } + Else { + Throw "gpupdate.exe failed with exit code [$($ExecuteResult.ExitCode)]." + } + } + $InstallCount++ + } + Catch { + Write-Log -Message "Failed to $($InstallMsg). `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to $($InstallMsg): $($_.Exception.Message)" + } + Continue + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Enable-TerminalServerInstallMode +Function Enable-TerminalServerInstallMode { +<# +.SYNOPSIS + Changes to user install mode for Remote Desktop Session Host/Citrix servers. +.DESCRIPTION + Changes to user install mode for Remote Desktop Session Host/Citrix servers. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Enable-TerminalServerInstallMode +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Change terminal server into user install mode...' -Source ${CmdletName} + $terminalServerResult = & "$envWinDir\System32\change.exe" User /Install + + If ($global:LastExitCode -ne 1) { Throw $terminalServerResult } + } + Catch { + Write-Log -Message "Failed to change terminal server into user install mode. `n$(Resolve-Error) " -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to change terminal server into user install mode: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Disable-TerminalServerInstallMode +Function Disable-TerminalServerInstallMode { +<# +.SYNOPSIS + Changes to user install mode for Remote Desktop Session Host/Citrix servers. +.DESCRIPTION + Changes to user install mode for Remote Desktop Session Host/Citrix servers. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Disable-TerminalServerInstallMode +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Change terminal server into user execute mode...' -Source ${CmdletName} + $terminalServerResult = & "$envWinDir\System32\change.exe" User /Execute + + If ($global:LastExitCode -ne 1) { Throw $terminalServerResult } + } + Catch { + Write-Log -Message "Failed to change terminal server into user execute mode. `n$(Resolve-Error) " -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to change terminal server into user execute mode: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-ActiveSetup +Function Set-ActiveSetup { +<# +.SYNOPSIS + Creates an Active Setup entry in the registry to execute a file for each user upon login. +.DESCRIPTION + Active Setup allows handling of per-user changes registry/file changes upon login. + A registry key is created in the HKLM registry hive which gets replicated to the HKCU hive when a user logs in. + If the "Version" value of the Active Setup entry in HKLM is higher than the version value in HKCU, the file referenced in "StubPath" is executed. + This Function: + - Creates the registry entries in HKLM:SOFTWARE\Microsoft\Active Setup\Installed Components\$installName. + - Creates StubPath value depending on the file extension of the $StubExePath parameter. + - Handles Version value with YYYYMMDDHHMMSS granularity to permit re-installs on the same day and still trigger Active Setup after Version increase. + - Copies/overwrites the StubPath file to $StubExePath destination path if file exists in 'Files' subdirectory of script directory. + - Executes the StubPath file for the current user as long as not in Session 0 (no need to logout/login to trigger Active Setup). +.PARAMETER StubExePath + Full destination path to the file that will be executed for each user that logs in. + If this file exists in the 'Files' subdirectory of the script directory, it will be copied to the destination path. +.PARAMETER Arguments + Arguments to pass to the file being executed. +.PARAMETER Description + Description for the Active Setup. Users will see "Setting up personalized settings for: $Description" at logon. Default is: $installName. +.PARAMETER Key + Name of the registry key for the Active Setup entry. Default is: $installName. +.PARAMETER Version + Optional. Specify version for Active setup entry. Active Setup is not triggered if Version value has more than 8 consecutive digits. Use commas to get around this limitation. +.PARAMETER Locale + Optional. Arbitrary string used to specify the installation language of the file being executed. Not replicated to HKCU. +.PARAMETER PurgeActiveSetupKey + Remove Active Setup entry from HKLM registry hive. Will also load each logon user's HKCU registry hive to remove Active Setup entry. +.PARAMETER DisableActiveSetup + Disables the Active Setup entry so that the StubPath file will not be executed. +.PARAMETER ExecuteForCurrentUser + Specifies whether the StubExePath should be executed for the current user. Since this user is already logged in, the user won't have the application started without logging out and logging back in. Default: $True +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Set-ActiveSetup -StubExePath 'C:\Users\Public\Company\ProgramUserConfig.vbs' -Arguments '/Silent' -Description 'Program User Config' -Key 'ProgramUserConfig' -Locale 'en' +.EXAMPLE + Set-ActiveSetup -StubExePath "$envWinDir\regedit.exe" -Arguments "/S `"%SystemDrive%\Program Files (x86)\PS App Deploy\PSAppDeployHKCUSettings.reg`"" -Description 'PS App Deploy Config' -Key 'PS_App_Deploy_Config' -ContinueOnError $true +.EXAMPLE + Set-ActiveSetup -Key 'ProgramUserConfig' -PurgeActiveSetupKey + Deletes "ProgramUserConfig" active setup entry from all registry hives. +.NOTES + Original code borrowed from: Denis St-Pierre (Ottawa, Canada), Todd MacNaught (Ottawa, Canada) +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [string]$StubExePath, + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [string]$Arguments, + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [string]$Description = $installName, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$Key = $installName, + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [string]$Version = ((Get-Date -Format 'yyMM,ddHH,mmss').ToString()), # Ex: 1405,1515,0522 + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [string]$Locale, + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [switch]$DisableActiveSetup = $false, + [Parameter(Mandatory=$true,ParameterSetName='Purge')] + [switch]$PurgeActiveSetupKey, + [Parameter(Mandatory=$false,ParameterSetName='Create')] + [ValidateNotNullorEmpty()] + [boolean]$ExecuteForCurrentUser = $true, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [boolean]$ContinueOnError = $true + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + [string]$ActiveSetupKey = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components\$Key" + [string]$HKCUActiveSetupKey = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Active Setup\Installed Components\$Key" + + ## Delete Active Setup registry entry from the HKLM hive and for all logon user registry hives on the system + If ($PurgeActiveSetupKey) { + Write-Log -Message "Removing Active Setup entry [$ActiveSetupKey]." -Source ${CmdletName} + Remove-RegistryKey -Key $ActiveSetupKey -Recurse + + Write-Log -Message "Removing Active Setup entry [$HKCUActiveSetupKey] for all log on user registry hives on the system." -Source ${CmdletName} + [scriptblock]$RemoveHKCUActiveSetupKey = { + If (Get-RegistryKey -Key $HKCUActiveSetupKey -SID $UserProfile.SID) { + Remove-RegistryKey -Key $HKCUActiveSetupKey -SID $UserProfile.SID -Recurse + } + } + Invoke-HKCURegistrySettingsForAllUsers -RegistrySettings $RemoveHKCUActiveSetupKey -UserProfiles (Get-UserProfiles -ExcludeDefaultUser) + Return + } + + ## Verify a file with a supported file extension was specified in $StubExePath + [string[]]$StubExePathFileExtensions = '.exe', '.vbs', '.cmd', '.ps1', '.js' + [string]$StubExeExt = [IO.Path]::GetExtension($StubExePath) + If ($StubExePathFileExtensions -notcontains $StubExeExt) { + Throw "Unsupported Active Setup StubPath file extension [$StubExeExt]." + } + + ## Copy file to $StubExePath from the 'Files' subdirectory of the script directory (if it exists there) + [string]$StubExePath = [Environment]::ExpandEnvironmentVariables($StubExePath) + [string]$ActiveSetupFileName = [IO.Path]::GetFileName($StubExePath) + [string]$StubExeFile = Join-Path -Path $dirFiles -ChildPath $ActiveSetupFileName + If (Test-Path -LiteralPath $StubExeFile -PathType 'Leaf') { + # This will overwrite the StubPath file if $StubExePath already exists on target + Copy-File -Path $StubExeFile -Destination $StubExePath -ContinueOnError $false + } + + ## Check if the $StubExePath file exists + If (-not (Test-Path -LiteralPath $StubExePath -PathType 'Leaf')) { Throw "Active Setup StubPath file [$ActiveSetupFileName] is missing." } + + ## Define Active Setup StubPath according to file extension of $StubExePath + Switch ($StubExeExt) { + '.exe' { + [string]$CUStubExePath = $StubExePath + [string]$CUArguments = $Arguments + [string]$StubPath = "$CUStubExePath" + } + {'.vbs','.js' -contains $StubExeExt} { + [string]$CUStubExePath = "$envWinDir\system32\cscript.exe" + [string]$CUArguments = "//nologo `"$StubExePath`"" + [string]$StubPath = "$CUStubExePath $CUArguments" + } + '.cmd' { + [string]$CUStubExePath = "$envWinDir\system32\CMD.exe" + [string]$CUArguments = "/C `"$StubExePath`"" + [string]$StubPath = "$CUStubExePath $CUArguments" + } + '.ps1' { + [string]$CUStubExePath = "$PSHOME\powershell.exe" + [string]$CUArguments = "-ExecutionPolicy Bypass -NoProfile -NoLogo -WindowStyle Hidden -Command `"& { & `\`"$StubExePath`\`"}`"" + [string]$StubPath = "$CUStubExePath $CUArguments" + } + } + If ($Arguments) { + [string]$StubPath = "$StubPath $Arguments" + If ($StubExeExt -ne '.exe') { [string]$CUArguments = "$CUArguments $Arguments" } + } + + ## Create the Active Setup entry in the registry + [scriptblock]$SetActiveSetupRegKeys = { + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullorEmpty()] + [string]$ActiveSetupRegKey, + [Parameter(Mandatory=$false)] + [ValidateNotNullorEmpty()] + [string]$SID + ) + If ($SID) { + Set-RegistryKey -Key $ActiveSetupRegKey -Name '(Default)' -Value $Description -SID $SID -ContinueOnError $false + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'StubPath' -Value $StubPath -Type 'String' -SID $SID -ContinueOnError $false + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'Version' -Value $Version -SID $SID -ContinueOnError $false + If ($Locale) { Set-RegistryKey -Key $ActiveSetupRegKey -Name 'Locale' -Value $Locale -SID $SID -ContinueOnError $false } + If ($DisableActiveSetup) { + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'IsInstalled' -Value 0 -Type 'DWord' -SID $SID -ContinueOnError $false + } + Else { + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'IsInstalled' -Value 1 -Type 'DWord' -SID $SID -ContinueOnError $false + } + } + Else { + Set-RegistryKey -Key $ActiveSetupRegKey -Name '(Default)' -Value $Description -ContinueOnError $false + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'StubPath' -Value $StubPath -Type 'String' -ContinueOnError $false + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'Version' -Value $Version -ContinueOnError $false + If ($Locale) { Set-RegistryKey -Key $ActiveSetupRegKey -Name 'Locale' -Value $Locale -ContinueOnError $false } + If ($DisableActiveSetup) { + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'IsInstalled' -Value 0 -Type 'DWord' -ContinueOnError $false + } + Else { + Set-RegistryKey -Key $ActiveSetupRegKey -Name 'IsInstalled' -Value 1 -Type 'DWord' -ContinueOnError $false + } + } + + } + + Write-Log -Message "Adding Active Setup Key for local machine: [$ActiveSetupKey]." -Source ${CmdletName} + & $SetActiveSetupRegKeys -ActiveSetupRegKey $ActiveSetupKey + + ## Execute the StubPath file for the current user as long as not in Session 0 + If ($SessionZero) { + If ($RunAsActiveUser) { + If ($ExecuteForCurrentUser) { + Write-Log -Message "Session 0 detected: Executing Active Setup StubPath file for currently logged in user [$($RunAsActiveUser.NTAccount)]." -Source ${CmdletName} + If ($CUArguments) { + Execute-ProcessAsUser -Path $CUStubExePath -Parameters $CUArguments -Wait -ContinueOnError $true + } + Else { + Execute-ProcessAsUser -Path $CUStubExePath -Wait -ContinueOnError $true + } + } + + Write-Log -Message "Adding Active Setup Key for the current user: [$HKCUActiveSetupKey]." -Source ${CmdletName} + & $SetActiveSetupRegKeys -ActiveSetupRegKey $HKCUActiveSetupKey -SID $RunAsActiveUser.SID + } + Else { + If ($ExecuteForCurrentUser) { + Write-Log -Message 'Session 0 detected: No logged in users detected. Active Setup StubPath file will execute when users first log into their account.' -Source ${CmdletName} + } + } + } + Else { + If ($ExecuteForCurrentUser) { + Write-Log -Message 'Executing Active Setup StubPath file for the current user.' -Source ${CmdletName} + If ($CUArguments) { + $ExecuteResults = Execute-Process -FilePath $CUStubExePath -Parameters $CUArguments -PassThru -ExitOnProcessFailure $false + } + Else { + $ExecuteResults = Execute-Process -FilePath $CUStubExePath -PassThru -ExitOnProcessFailure $false + } + } + + Write-Log -Message "Adding Active Setup Key for the current user: [$HKCUActiveSetupKey]." -Source ${CmdletName} + & $SetActiveSetupRegKeys -ActiveSetupRegKey $HKCUActiveSetupKey + } + } + Catch { + Write-Log -Message "Failed to set Active Setup registry entry. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed to set Active Setup registry entry: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Test-ServiceExists +Function Test-ServiceExists { +<# +.SYNOPSIS + Check to see if a service exists. +.DESCRIPTION + Check to see if a service exists (using WMI method because Get-Service will generate ErrorRecord if service doesn't exist). +.PARAMETER Name + Specify the name of the service. + Note: Service name can be found by executing "Get-Service | Format-Table -AutoSize -Wrap" or by using the properties screen of a service in services.msc. +.PARAMETER ComputerName + Specify the name of the computer. Default is: the local computer. +.PARAMETER PassThru + Return the WMI service object. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Test-ServiceExists -Name 'wuauserv' +.EXAMPLE + Test-ServiceExists -Name 'testservice' -PassThru | Where-Object { $_ } | ForEach-Object { $_.Delete() } + Check if a service exists and then delete it by using the -PassThru parameter. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$ComputerName = $env:ComputerName, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$PassThru, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + Begin { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + $ServiceObject = Get-WmiObject -ComputerName $ComputerName -Class 'Win32_Service' -Filter "Name='$Name'" -ErrorAction 'Stop' + # If nothing is returned from Win32_Service, check Win32_BaseService + If (-not ($ServiceObject) ) { + $ServiceObject = Get-WmiObject -ComputerName $ComputerName -Class 'Win32_BaseService' -Filter "Name='$Name'" -ErrorAction 'Stop' + } + + If ($ServiceObject) { + Write-Log -Message "Service [$Name] exists." -Source ${CmdletName} + If ($PassThru) { Write-Output -InputObject $ServiceObject } Else { Write-Output -InputObject $true } + } + Else { + Write-Log -Message "Service [$Name] does not exist." -Source ${CmdletName} + If ($PassThru) { Write-Output -InputObject $ServiceObject } Else { Write-Output -InputObject $false } + } + } + Catch { + Write-Log -Message "Failed check to see if service [$Name] exists." -Severity 3 -Source ${CmdletName} + If (-not $ContinueOnError) { + Throw "Failed check to see if service [$Name] exists: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Stop-ServiceAndDependencies +Function Stop-ServiceAndDependencies { +<# +.SYNOPSIS + Stop Windows service and its dependencies. +.DESCRIPTION + Stop Windows service and its dependencies. +.PARAMETER Name + Specify the name of the service. +.PARAMETER ComputerName + Specify the name of the computer. Default is: the local computer. +.PARAMETER SkipServiceExistsTest + Choose to skip the test to check whether or not the service exists if it was already done outside of this function. +.PARAMETER SkipDependentServices + Choose to skip checking for and stopping dependent services. Default is: $false. +.PARAMETER PendingStatusWait + The amount of time to wait for a service to get out of a pending state before continuing. Default is 60 seconds. +.PARAMETER PassThru + Return the System.ServiceProcess.ServiceController service object. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Stop-ServiceAndDependencies -Name 'wuauserv' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$ComputerName = $env:ComputerName, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$SkipServiceExistsTest, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$SkipDependentServices, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [timespan]$PendingStatusWait = (New-TimeSpan -Seconds 60), + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$PassThru, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + Begin { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## Check to see if the service exists + If ((-not $SkipServiceExistsTest) -and (-not (Test-ServiceExists -ComputerName $ComputerName -Name $Name -ContinueOnError $false))) { + Write-Log -Message "Service [$Name] does not exist." -Source ${CmdletName} -Severity 2 + Throw "Service [$Name] does not exist." + } + + ## Get the service object + Write-Log -Message "Get the service object for service [$Name]." -Source ${CmdletName} + [ServiceProcess.ServiceController]$Service = Get-Service -ComputerName $ComputerName -Name $Name -ErrorAction 'Stop' + ## Wait up to 60 seconds if service is in a pending state + [string[]]$PendingStatus = 'ContinuePending', 'PausePending', 'StartPending', 'StopPending' + If ($PendingStatus -contains $Service.Status) { + Switch ($Service.Status) { + 'ContinuePending' { $DesiredStatus = 'Running' } + 'PausePending' { $DesiredStatus = 'Paused' } + 'StartPending' { $DesiredStatus = 'Running' } + 'StopPending' { $DesiredStatus = 'Stopped' } + } + Write-Log -Message "Waiting for up to [$($PendingStatusWait.TotalSeconds)] seconds to allow service pending status [$($Service.Status)] to reach desired status [$DesiredStatus]." -Source ${CmdletName} + $Service.WaitForStatus([ServiceProcess.ServiceControllerStatus]$DesiredStatus, $PendingStatusWait) + $Service.Refresh() + } + ## Discover if the service is currently running + Write-Log -Message "Service [$($Service.ServiceName)] with display name [$($Service.DisplayName)] has a status of [$($Service.Status)]." -Source ${CmdletName} + If ($Service.Status -ne 'Stopped') { + # Discover all dependent services that are running and stop them + If (-not $SkipDependentServices) { + Write-Log -Message "Discover all dependent service(s) for service [$Name] which are not 'Stopped'." -Source ${CmdletName} + [ServiceProcess.ServiceController[]]$DependentServices = Get-Service -ComputerName $ComputerName -Name $Service.ServiceName -DependentServices -ErrorAction 'Stop' | Where-Object { $_.Status -ne 'Stopped' } + If ($DependentServices) { + ForEach ($DependentService in $DependentServices) { + Write-Log -Message "Stop dependent service [$($DependentService.ServiceName)] with display name [$($DependentService.DisplayName)] and a status of [$($DependentService.Status)]." -Source ${CmdletName} + Try { + Stop-Service -InputObject (Get-Service -ComputerName $ComputerName -Name $DependentService.ServiceName -ErrorAction 'Stop') -Force -WarningAction 'SilentlyContinue' -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to start dependent service [$($DependentService.ServiceName)] with display name [$($DependentService.DisplayName)] and a status of [$($DependentService.Status)]. Continue..." -Severity 2 -Source ${CmdletName} + Continue + } + } + } + Else { + Write-Log -Message "Dependent service(s) were not discovered for service [$Name]." -Source ${CmdletName} + } + } + # Stop the parent service + Write-Log -Message "Stop parent service [$($Service.ServiceName)] with display name [$($Service.DisplayName)]." -Source ${CmdletName} + [ServiceProcess.ServiceController]$Service = Stop-Service -InputObject (Get-Service -ComputerName $ComputerName -Name $Service.ServiceName -ErrorAction 'Stop') -Force -PassThru -WarningAction 'SilentlyContinue' -ErrorAction 'Stop' + } + } + Catch { + Write-Log -Message "Failed to stop the service [$Name]. `n$(Resolve-Error)" -Source ${CmdletName} -Severity 3 + If (-not $ContinueOnError) { + Throw "Failed to stop the service [$Name]: $($_.Exception.Message)" + } + } + Finally { + # Return the service object if option selected + If ($PassThru -and $Service) { Write-Output -InputObject $Service } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Start-ServiceAndDependencies +Function Start-ServiceAndDependencies { +<# +.SYNOPSIS + Start Windows service and its dependencies. +.DESCRIPTION + Start Windows service and its dependencies. +.PARAMETER Name + Specify the name of the service. +.PARAMETER ComputerName + Specify the name of the computer. Default is: the local computer. +.PARAMETER SkipServiceExistsTest + Choose to skip the test to check whether or not the service exists if it was already done outside of this function. +.PARAMETER SkipDependentServices + Choose to skip checking for and starting dependent services. Default is: $false. +.PARAMETER PendingStatusWait + The amount of time to wait for a service to get out of a pending state before continuing. Default is 60 seconds. +.PARAMETER PassThru + Return the System.ServiceProcess.ServiceController service object. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Start-ServiceAndDependencies -Name 'wuauserv' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$ComputerName = $env:ComputerName, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$SkipServiceExistsTest, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$SkipDependentServices, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [timespan]$PendingStatusWait = (New-TimeSpan -Seconds 60), + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [switch]$PassThru, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + Begin { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## Check to see if the service exists + If ((-not $SkipServiceExistsTest) -and (-not (Test-ServiceExists -ComputerName $ComputerName -Name $Name -ContinueOnError $false))) { + Write-Log -Message "Service [$Name] does not exist." -Source ${CmdletName} -Severity 2 + Throw "Service [$Name] does not exist." + } + + ## Get the service object + Write-Log -Message "Get the service object for service [$Name]." -Source ${CmdletName} + [ServiceProcess.ServiceController]$Service = Get-Service -ComputerName $ComputerName -Name $Name -ErrorAction 'Stop' + ## Wait up to 60 seconds if service is in a pending state + [string[]]$PendingStatus = 'ContinuePending', 'PausePending', 'StartPending', 'StopPending' + If ($PendingStatus -contains $Service.Status) { + Switch ($Service.Status) { + 'ContinuePending' { $DesiredStatus = 'Running' } + 'PausePending' { $DesiredStatus = 'Paused' } + 'StartPending' { $DesiredStatus = 'Running' } + 'StopPending' { $DesiredStatus = 'Stopped' } + } + Write-Log -Message "Waiting for up to [$($PendingStatusWait.TotalSeconds)] seconds to allow service pending status [$($Service.Status)] to reach desired status [$DesiredStatus]." -Source ${CmdletName} + $Service.WaitForStatus([ServiceProcess.ServiceControllerStatus]$DesiredStatus, $PendingStatusWait) + $Service.Refresh() + } + ## Discover if the service is currently stopped + Write-Log -Message "Service [$($Service.ServiceName)] with display name [$($Service.DisplayName)] has a status of [$($Service.Status)]." -Source ${CmdletName} + If ($Service.Status -ne 'Running') { + # Start the parent service + Write-Log -Message "Start parent service [$($Service.ServiceName)] with display name [$($Service.DisplayName)]." -Source ${CmdletName} + [ServiceProcess.ServiceController]$Service = Start-Service -InputObject (Get-Service -ComputerName $ComputerName -Name $Service.ServiceName -ErrorAction 'Stop') -PassThru -WarningAction 'SilentlyContinue' -ErrorAction 'Stop' + + # Discover all dependent services that are stopped and start them + If (-not $SkipDependentServices) { + Write-Log -Message "Discover all dependent service(s) for service [$Name] which are not 'Running'." -Source ${CmdletName} + [ServiceProcess.ServiceController[]]$DependentServices = Get-Service -ComputerName $ComputerName -Name $Service.ServiceName -DependentServices -ErrorAction 'Stop' | Where-Object { $_.Status -ne 'Running' } + If ($DependentServices) { + ForEach ($DependentService in $DependentServices) { + Write-Log -Message "Start dependent service [$($DependentService.ServiceName)] with display name [$($DependentService.DisplayName)] and a status of [$($DependentService.Status)]." -Source ${CmdletName} + Try { + Start-Service -InputObject (Get-Service -ComputerName $ComputerName -Name $DependentService.ServiceName -ErrorAction 'Stop') -WarningAction 'SilentlyContinue' -ErrorAction 'Stop' + } + Catch { + Write-Log -Message "Failed to start dependent service [$($DependentService.ServiceName)] with display name [$($DependentService.DisplayName)] and a status of [$($DependentService.Status)]. Continue..." -Severity 2 -Source ${CmdletName} + Continue + } + } + } + Else { + Write-Log -Message "Dependent service(s) were not discovered for service [$Name]." -Source ${CmdletName} + } + } + } + } + Catch { + Write-Log -Message "Failed to start the service [$Name]. `n$(Resolve-Error)" -Source ${CmdletName} -Severity 3 + If (-not $ContinueOnError) { + Throw "Failed to start the service [$Name]: $($_.Exception.Message)" + } + } + Finally { + # Return the service object if option selected + If ($PassThru -and $Service) { Write-Output -InputObject $Service } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-ServiceStartMode +Function Get-ServiceStartMode +{ +<# +.SYNOPSIS + Get the service startup mode. +.DESCRIPTION + Get the service startup mode. +.PARAMETER Name + Specify the name of the service. +.PARAMETER ComputerName + Specify the name of the computer. Default is: the local computer. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Get-ServiceStartMode -Name 'wuauserv' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdLetBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$ComputerName = $env:ComputerName, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + Begin { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message "Get the service [$Name] startup mode." -Source ${CmdletName} + [string]$ServiceStartMode = (Get-WmiObject -ComputerName $ComputerName -Class 'Win32_Service' -Filter "Name='$Name'" -Property 'StartMode' -ErrorAction 'Stop').StartMode + ## If service start mode is set to 'Auto', change value to 'Automatic' to be consistent with 'Set-ServiceStartMode' function + If ($ServiceStartMode -eq 'Auto') { $ServiceStartMode = 'Automatic'} + + ## If on Windows Vista or higher, check to see if service is set to Automatic (Delayed Start) + If (($ServiceStartMode -eq 'Automatic') -and (([version]$envOSVersion).Major -gt 5)) { + Try { + [string]$ServiceRegistryPath = "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\$Name" + [int32]$DelayedAutoStart = Get-ItemProperty -LiteralPath $ServiceRegistryPath -ErrorAction 'Stop' | Select-Object -ExpandProperty 'DelayedAutoStart' -ErrorAction 'Stop' + If ($DelayedAutoStart -eq 1) { $ServiceStartMode = 'Automatic (Delayed Start)' } + } + Catch { } + } + + Write-Log -Message "Service [$Name] startup mode is set to [$ServiceStartMode]." -Source ${CmdletName} + Write-Output -InputObject $ServiceStartMode + } + Catch { + Write-Log -Message "Failed to get the service [$Name] startup mode. `n$(Resolve-Error)" -Source ${CmdletName} -Severity 3 + If (-not $ContinueOnError) { + Throw "Failed to get the service [$Name] startup mode: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Set-ServiceStartMode +Function Set-ServiceStartMode +{ +<# +.SYNOPSIS + Set the service startup mode. +.DESCRIPTION + Set the service startup mode. +.PARAMETER Name + Specify the name of the service. +.PARAMETER ComputerName + Specify the name of the computer. Default is: the local computer. +.PARAMETER StartMode + Specify startup mode for the service. Options: Automatic, Automatic (Delayed Start), Manual, Disabled, Boot, System. +.PARAMETER ContinueOnError + Continue if an error is encountered. Default is: $true. +.EXAMPLE + Set-ServiceStartMode -Name 'wuauserv' -StartMode 'Automatic (Delayed Start)' +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdLetBinding()] + Param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$Name, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [string]$ComputerName = $env:ComputerName, + [Parameter(Mandatory=$true)] + [ValidateSet('Automatic','Automatic (Delayed Start)','Manual','Disabled','Boot','System')] + [string]$StartMode, + [Parameter(Mandatory=$false)] + [ValidateNotNullOrEmpty()] + [boolean]$ContinueOnError = $true + ) + Begin { + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + ## If on lower than Windows Vista and 'Automatic (Delayed Start)' selected, then change to 'Automatic' because 'Delayed Start' is not supported. + If (($StartMode -eq 'Automatic (Delayed Start)') -and (([version]$envOSVersion).Major -lt 6)) { $StartMode = 'Automatic' } + + Write-Log -Message "Set service [$Name] startup mode to [$StartMode]." -Source ${CmdletName} + + ## Set the name of the start up mode that will be passed to sc.exe + [string]$ScExeStartMode = $StartMode + If ($StartMode -eq 'Automatic') { $ScExeStartMode = 'Auto' } + If ($StartMode -eq 'Automatic (Delayed Start)') { $ScExeStartMode = 'Delayed-Auto' } + If ($StartMode -eq 'Manual') { $ScExeStartMode = 'Demand' } + + ## Set the start up mode using sc.exe. Note: we found that the ChangeStartMode method in the Win32_Service WMI class set services to 'Automatic (Delayed Start)' even when you specified 'Automatic' on Win7, Win8, and Win10. + $ChangeStartMode = & "$envWinDir\System32\sc.exe" config $Name start= $ScExeStartMode + + If ($global:LastExitCode -ne 0) { + Throw "sc.exe failed with exit code [$($global:LastExitCode)] and message [$ChangeStartMode]." + } + + Write-Log -Message "Successfully set service [$Name] startup mode to [$StartMode]." -Source ${CmdletName} + } + Catch { + Write-Log -Message "Failed to set service [$Name] startup mode to [$StartMode]. `n$(Resolve-Error)" -Source ${CmdletName} -Severity 3 + If (-not $ContinueOnError) { + Throw "Failed to set service [$Name] startup mode to [$StartMode]: $($_.Exception.Message)" + } + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-LoggedOnUser +Function Get-LoggedOnUser { +<# +.SYNOPSIS + Get session details for all local and RDP logged on users. +.DESCRIPTION + Get session details for all local and RDP logged on users using Win32 APIs. Get the following session details: + NTAccount, SID, UserName, DomainName, SessionId, SessionName, ConnectState, IsCurrentSession, IsConsoleSession, IsUserSession, IsActiveUserSession + IsRdpSession, IsLocalAdmin, LogonTime, IdleTime, DisconnectTime, ClientName, ClientProtocolType, ClientDirectory, ClientBuildNumber +.EXAMPLE + Get-LoggedOnUser +.NOTES + Description of ConnectState property: + Value Description + ----- ----------- + Active A user is logged on to the session. + ConnectQuery The session is in the process of connecting to a client. + Connected A client is connected to the session. + Disconnected The session is active, but the client has disconnected from it. + Down The session is down due to an error. + Idle The session is waiting for a client to connect. + Initializing The session is initializing. + Listening The session is listening for connections. + Reset The session is being reset. + Shadowing This session is shadowing another session. + + Description of IsActiveUserSession property: + If a console user exists, then that will be the active user session. + If no console user exists but users are logged in, such as on terminal servers, then the first logged-in non-console user that is either 'Active' or 'Connected' is the active user. + + Description of IsRdpSession property: + Gets a value indicating whether the user is associated with an RDP client session. +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + Process { + Try { + Write-Log -Message 'Get session information for all logged on users.' -Source ${CmdletName} + Write-Output -InputObject ([PSADT.QueryUser]::GetUserSessionInfo("$env:ComputerName")) + } + Catch { + Write-Log -Message "Failed to get session information for all logged on users. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + + +#region Function Get-PendingReboot +Function Get-PendingReboot { +<# +.SYNOPSIS + Get the pending reboot status on a local computer. +.DESCRIPTION + Check WMI and the registry to determine if the system has a pending reboot operation from any of the following: + a) Component Based Servicing (Vista, Windows 2008) + b) Windows Update / Auto Update (XP, Windows 2003 / 2008) + c) SCCM 2012 Clients (DetermineIfRebootPending WMI method) + d) App-V Pending Tasks (global based Appv 5.0 SP2) + e) Pending File Rename Operations (XP, Windows 2003 / 2008) +.EXAMPLE + Get-PendingReboot + + Returns custom object with following properties: + ComputerName, LastBootUpTime, IsSystemRebootPending, IsCBServicingRebootPending, IsWindowsUpdateRebootPending, IsSCCMClientRebootPending, IsFileRenameRebootPending, PendingFileRenameOperations, ErrorMsg + + *Notes: ErrorMsg only contains something if an error occurred +.EXAMPLE + (Get-PendingReboot).IsSystemRebootPending + Returns boolean value determining whether or not there is a pending reboot operation. +.NOTES +.LINK + http://psappdeploytoolkit.com +#> + [CmdletBinding()] + Param ( + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + + ## Initialize variables + [string]$private:ComputerName = $envComputerNameFQDN + $PendRebootErrorMsg = $null + } + Process { + Write-Log -Message "Get the pending reboot status on the local computer [$ComputerName]." -Source ${CmdletName} + + ## Get the date/time that the system last booted up + Try { + [nullable[datetime]]$LastBootUpTime = (Get-Date -ErrorAction 'Stop') - ([timespan]::FromMilliseconds([math]::Abs([Environment]::TickCount))) + } + Catch { + [nullable[datetime]]$LastBootUpTime = $null + [string[]]$PendRebootErrorMsg += "Failed to get LastBootUpTime: $($_.Exception.Message)" + Write-Log -Message "Failed to get LastBootUpTime. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Determine if a Windows Vista/Server 2008 and above machine has a pending reboot from a Component Based Servicing (CBS) operation + Try { + If (([version]$envOSVersion).Major -ge 5) { + If (Test-Path -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' -ErrorAction 'Stop') { + [nullable[boolean]]$IsCBServicingRebootPending = $true + } + Else { + [nullable[boolean]]$IsCBServicingRebootPending = $false + } + } + } + Catch { + [nullable[boolean]]$IsCBServicingRebootPending = $null + [string[]]$PendRebootErrorMsg += "Failed to get IsCBServicingRebootPending: $($_.Exception.Message)" + Write-Log -Message "Failed to get IsCBServicingRebootPending. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Determine if there is a pending reboot from a Windows Update + Try { + If (Test-Path -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' -ErrorAction 'Stop') { + [nullable[boolean]]$IsWindowsUpdateRebootPending = $true + } + Else { + [nullable[boolean]]$IsWindowsUpdateRebootPending = $false + } + } + Catch { + [nullable[boolean]]$IsWindowsUpdateRebootPending = $null + [string[]]$PendRebootErrorMsg += "Failed to get IsWindowsUpdateRebootPending: $($_.Exception.Message)" + Write-Log -Message "Failed to get IsWindowsUpdateRebootPending. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Determine if there is a pending reboot from a pending file rename operation + [boolean]$IsFileRenameRebootPending = $false + $PendingFileRenameOperations = $null + If (Test-RegistryValue -Key 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations') { + # If PendingFileRenameOperations value exists, set $IsFileRenameRebootPending variable to $true + [boolean]$IsFileRenameRebootPending = $true + # Get the value of PendingFileRenameOperations + Try { + [string[]]$PendingFileRenameOperations = Get-ItemProperty -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'PendingFileRenameOperations' -ErrorAction 'Stop' + } + Catch { + [string[]]$PendRebootErrorMsg += "Failed to get PendingFileRenameOperations: $($_.Exception.Message)" + Write-Log -Message "Failed to get PendingFileRenameOperations. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + } + + ## Determine SCCM 2012 Client reboot pending status + Try { + [boolean]$IsSccmClientNamespaceExists = $false + [psobject]$SCCMClientRebootStatus = Invoke-WmiMethod -ComputerName $ComputerName -NameSpace 'ROOT\CCM\ClientSDK' -Class 'CCM_ClientUtilities' -Name 'DetermineIfRebootPending' -ErrorAction 'Stop' + [boolean]$IsSccmClientNamespaceExists = $true + If ($SCCMClientRebootStatus.ReturnValue -ne 0) { + Throw "'DetermineIfRebootPending' method of 'ROOT\CCM\ClientSDK\CCM_ClientUtilities' class returned error code [$($SCCMClientRebootStatus.ReturnValue)]" + } + Else { + Write-Log -Message 'Successfully queried SCCM client for reboot status.' -Source ${CmdletName} + [nullable[boolean]]$IsSCCMClientRebootPending = $false + If ($SCCMClientRebootStatus.IsHardRebootPending -or $SCCMClientRebootStatus.RebootPending) { + [nullable[boolean]]$IsSCCMClientRebootPending = $true + Write-Log -Message 'Pending SCCM reboot detected.' -Source ${CmdletName} + } + Else { + Write-Log -Message 'Pending SCCM reboot not detected.' -Source ${CmdletName} + } + } + } + Catch [System.Management.ManagementException] { + [nullable[boolean]]$IsSCCMClientRebootPending = $null + [boolean]$IsSccmClientNamespaceExists = $false + Write-Log -Message "Failed to get IsSCCMClientRebootPending. Failed to detect the SCCM client WMI class." -Severity 3 -Source ${CmdletName} + } + Catch { + [nullable[boolean]]$IsSCCMClientRebootPending = $null + [string[]]$PendRebootErrorMsg += "Failed to get IsSCCMClientRebootPending: $($_.Exception.Message)" + Write-Log -Message "Failed to get IsSCCMClientRebootPending. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Determine if there is a pending reboot from an App-V global Pending Task. (User profile based tasks will complete on logoff/logon) + Try { + If (Test-Path -LiteralPath 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Software\Microsoft\AppV\Client\PendingTasks' -ErrorAction 'Stop') { + + [nullable[boolean]]$IsAppVRebootPending = $true + } + Else { + [nullable[boolean]]$IsAppVRebootPending = $false + } + } + Catch { + [nullable[boolean]]$IsAppVRebootPending = $null + [string[]]$PendRebootErrorMsg += "Failed to get IsAppVRebootPending: $($_.Exception.Message)" + Write-Log -Message "Failed to get IsAppVRebootPending. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} + } + + ## Determine if there is a pending reboot for the system + [boolean]$IsSystemRebootPending = $false + If ($IsCBServicingRebootPending -or $IsWindowsUpdateRebootPending -or $IsSCCMClientRebootPending -or $IsFileRenameRebootPending) { + [boolean]$IsSystemRebootPending = $true + } + + ## Create a custom object containing pending reboot information for the system + [psobject]$PendingRebootInfo = New-Object -TypeName 'PSObject' -Property @{ + ComputerName = $ComputerName + LastBootUpTime = $LastBootUpTime + IsSystemRebootPending = $IsSystemRebootPending + IsCBServicingRebootPending = $IsCBServicingRebootPending + IsWindowsUpdateRebootPending = $IsWindowsUpdateRebootPending + IsSCCMClientRebootPending = $IsSCCMClientRebootPending + IsAppVRebootPending = $IsAppVRebootPending + IsFileRenameRebootPending = $IsFileRenameRebootPending + PendingFileRenameOperations = $PendingFileRenameOperations + ErrorMsg = $PendRebootErrorMsg + } + Write-Log -Message "Pending reboot status on the local computer [$ComputerName]: `n$($PendingRebootInfo | Format-List | Out-String)" -Source ${CmdletName} + } + End { + Write-Output -InputObject ($PendingRebootInfo | Select-Object -Property 'ComputerName','LastBootUpTime','IsSystemRebootPending','IsCBServicingRebootPending','IsWindowsUpdateRebootPending','IsSCCMClientRebootPending','IsAppVRebootPending','IsFileRenameRebootPending','PendingFileRenameOperations','ErrorMsg') + + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + +#region Function Set-ItemPermission +Function Set-ItemPermission { + <# + .SYNOPSIS + Allow you to easily change permissions on files or folders + .PARAMETER Path + Path to the folder or file you want to modify (ex: C:\Temp) + .PARAMETER User + One or more user names (ex: BUILTIN\Users, DOMAIN\Admin) to give the permissions to. If you want to use SID, prefix it with an asterisk * (ex: *S-1-5-18) + .PARAMETER Permission + Permission or list of permissions to be set/added/removed/replaced. To see all the possible permissions go to 'http://technet.microsoft.com/fr-fr/library/ff730951.aspx'. + Permission DeleteSubdirectoriesAndFiles does not apply to files. + .PARAMETER PermissionType + Sets Access Control Type of the permissions. Allowed options: Allow, Deny Default: Allow + .PARAMETER Inheritance + Sets permission inheritance. Does not apply to files. Multiple options can be specified. Allowed options: ObjectInherit, ContainerInherit, None Default: None + None - The permission entry is not inherited by child objects, ObjectInherit - The permission entry is inherited by child leaf objects. ContainerInherit - The permission entry is inherited by child container objects. + .PARAMETER Propagation + Sets how to propagate inheritance. Does not apply to files. Allowed options: None, InheritOnly, NoPropagateInherit Default: None + None - Specifies that no inheritance flags are set. NoPropagateInherit - Specifies that the permission entry is not propagated to child objects. InheritOnly - Specifies that the permission entry is propagated only to child objects. This includes both container and leaf child objects. + .PARAMETER Method + Specifies which method will be used to apply the permissions. Allowed options: Add, Set, Reset. + Add - adds permissions rules but it does not remove previous permissions, Set - overwrites matching permission rules with new ones, Reset - removes matching permissions rules and then adds permission rules, Remove - Removes matching permission rules, RemoveSpecific - Removes specific permissions, RemoveAll - Removes all permission rules for specified user/s + Default: Add + .PARAMETER EnableInheritance + Enables inheritance on the files/folders. + .EXAMPLE + Will grant FullControl permissions to 'John' and 'Users' on 'C:\Temp' and its files and folders children. + PS C:\>Set-ItemPermission -Path "C:\Temp" -User "DOMAIN\John", "BUILTIN\Utilisateurs" -Permission FullControl -Inheritance ObjectInherit,ContainerInherit + .EXAMPLE + Will grant Read permissions to 'John' on 'C:\Temp\pic.png' + PS C:\>Set-ItemPermission -Path "C:\Temp\pic.png" -User "DOMAIN\John" -Permission Read + .EXAMPLE + Will remove all permissions to 'John' on 'C:\Temp\Private' + PS C:\>Set-ItemPermission -Path "C:\Temp\Private" -User "DOMAIN\John" -Permission None -Method RemoveAll + .NOTES + Original Author : Julian DA CUNHA - dacunha.julian@gmail.com, used with permission + .LINK + http://psappdeploytoolkit.com + #> + + [CmdletBinding()] + Param ( + [Parameter( Mandatory=$True, Position=0, HelpMessage = "Path to the folder or file you want to modify (ex: C:\Temp)",ParameterSetName="DisableInheritance" )] + [Parameter( Mandatory=$True, Position=0, HelpMessage = "Path to the folder or file you want to modify (ex: C:\Temp)",ParameterSetName="EnableInheritance" )] + [ValidateNotNullOrEmpty()] + [Alias('File', 'Folder')] + [String]$Path, + + [Parameter( Mandatory=$True, Position=1, HelpMessage = "One or more user names (ex: BUILTIN\Users, DOMAIN\Admin). If you want to use SID, prefix it with an asterisk * (ex: *S-1-5-18)", ParameterSetName="DisableInheritance")] + [Alias('Username', 'Users', 'SID', 'Usernames')] + [String[]]$User, + + [Parameter( Mandatory=$True, Position=2, HelpMessage = "Permission or list of permissions to be set/added/removed/replaced. To see all the possible permissions go to 'http://technet.microsoft.com/fr-fr/library/ff730951.aspx'", ParameterSetName="DisableInheritance")] + [Alias('Acl', 'Grant', 'Permissions', 'Deny')] + [ValidateSet("AppendData", "ChangePermissions", "CreateDirectories", "CreateFiles", "Delete", ` + "DeleteSubdirectoriesAndFiles", "ExecuteFile", "FullControl", "ListDirectory", "Modify",` + "Read", "ReadAndExecute", "ReadAttributes", "ReadData", "ReadExtendedAttributes", "ReadPermissions",` + "Synchronize", "TakeOwnership", "Traverse", "Write", "WriteAttributes", "WriteData", "WriteExtendedAttributes", "None")] + [String[]]$Permission, + + [Parameter( Mandatory=$False, Position=3, HelpMessage = "Whether you want to set Allow or Deny permissions", ParameterSetName="DisableInheritance")] + [Alias('AccessControlType')] + [ValidateSet("Allow", "Deny")] + [String]$PermissionType = "Allow", + + [Parameter( Mandatory=$False, Position=4, HelpMessage = "Sets how permissions are inherited", ParameterSetName="DisableInheritance")] + [ValidateSet("ContainerInherit", "None", "ObjectInherit")] + [String[]]$Inheritance = "None", + + [Parameter( Mandatory=$False, Position=5, HelpMessage = "Sets how to propage inheritance flags", ParameterSetName="DisableInheritance")] + [ValidateSet("None", "InheritOnly", "NoPropagateInherit")] + [String]$Propagation = "None", + + [Parameter( Mandatory=$False, Position=6, HelpMessage = "Specifies which method will be used to add/remove/replace permissions.", ParameterSetName="DisableInheritance")] + [ValidateSet("Add", "Set", "Reset", "Remove", "RemoveSpecific", "RemoveAll")] + [Alias("ApplyMethod", "ApplicationMethod")] + [String]$Method = "Add", + + [Parameter( Mandatory=$True, Position=1, HelpMessage = "Enables inheritance, which removes explicit permissions.", ParameterSetName="EnableInheritance")] + [switch]$EnableInheritance + ) + + Begin { + ## Get the name of this function and write header + [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header + } + + Process { + # Test elevated perms + If (-not $IsAdmin){ + Write-Log -Message "Unable to use the function [Set-ItemPermission] without elevated permissions." -Source ${CmdletName} + Throw "Unable to use the function [Set-ItemPermission] without elevated permissions." + } + + # Check path existence + If (-not (Test-Path -Path $Path -ErrorAction Stop)) { + Write-Log -Message "Specified path does not exist [$Path]." -Source ${CmdletName} + Throw "Specified path does not exist [$Path]." + } + + If ($EnableInheritance) { + # Get object acls + $Acl = (get-item -Path $Path -ErrorAction Stop).GetAccessControl('Access') + # Enable inherance + $Acl.SetAccessRuleProtection($False, $True) + Write-Log -Message "Enabling Inheritance on path [$Path]." -Source ${CmdletName} + $null = Set-Acl -Path $Path -AclObject $Acl -ErrorAction Stop + return + } + # Permissions + [System.Security.AccessControl.FileSystemRights]$FileSystemRights = New-Object System.Security.AccessControl.FileSystemRights + If ($Permission -ne "None") { + foreach ($Entry in $Permission) { + $FileSystemRights = $FileSystemRights -bor [System.Security.AccessControl.FileSystemRights]$Entry + } + } + + # InheritanceFlags + $InheritanceFlag = New-Object System.Security.AccessControl.InheritanceFlags + foreach ($IFlag in $Inheritance) { + $InheritanceFlag = $InheritanceFlag -bor [System.Security.AccessControl.InheritanceFlags]$IFlag + } + + # PropagationFlags + $PropagationFlag = [System.Security.AccessControl.PropagationFlags]$Propagation + + # Access Control Type + $Allow = [System.Security.AccessControl.AccessControlType]$PermissionType + + # Modify variables to remove file incompatible flags if this is a file + If (Test-Path -Path $Path -ErrorAction Stop -PathType Leaf) { + $FileSystemRights = $FileSystemRights -band (-bnot [System.Security.AccessControl.FileSystemRights]::DeleteSubdirectoriesAndFiles) + $InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None + $PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None + } + + # Get object acls + $Acl = (get-item -Path $Path -ErrorAction Stop).GetAccessControl('Access') + # Disable inherance, Preserve inherited permissions + $Acl.SetAccessRuleProtection($True, $True) + $null = Set-Acl -Path $Path -AclObject $Acl -ErrorAction Stop + # Get updated acls - without inheritance + $Acl = $null + $Acl = (get-item -Path $Path -ErrorAction Stop).GetAccessControl('Access') + # Apply permissions on Users + Foreach ($U in $User) { + # Trim whitespace and skip if empty + $U = $U.Trim() + If ($U.Length -eq 0) { + continue + } + # Set Username + If ($U.StartsWith('*')) { + # This is a SID, remove the * + $U = $U.remove(0,1) + try { + # Translate the SID + $Username = ConvertTo-NTAccountOrSID -SID $U + } + catch { + Write-Log "Failed to translate SID [$U]. Skipping..." -Source ${CmdletName} -Severity 2 + continue + } + + $Username = New-Object System.Security.Principal.NTAccount($UsersAccountName) + } else { + $Username = New-Object System.Security.Principal.NTAccount($U) + } + + # Set/Add/Remove/Replace permissions and log the changes + $Rule = New-Object System.Security.AccessControl.FileSystemAccessRule($Username, $FileSystemRights, $InheritanceFlag, $PropagationFlag, $Allow) + switch ($Method) { + "Add" { + Write-Log -Message "Setting permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.AddAccessRule($Rule) + break + } + "Set" { + Write-Log -Message "Setting permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.SetAccessRule($Rule) + break + } + "Reset" { + Write-Log -Message "Setting permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.ResetAccessRule($Rule) + break + } + "Remove" { + Write-Log -Message "Removing permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.RemoveAccessRule($Rule) + break + } + "RemoveSpecific" { + Write-Log -Message "Removing permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.RemoveAccessRuleSpecific($Rule) + break + } + "RemoveAll" { + Write-Log -Message "Removing permissions [Permissions:$FileSystemRights, InheritanceFlags:$InheritanceFlag, PropagationFlags:$PropagationFlag, AccessControlType:$Allow, Method:$Method] on path [$Path] for user [$Username]." -Source ${CmdletName} + $Acl.RemoveAccessRuleAll($Rule) + break + } + } + } + # Use the prepared ACL + $null = Set-Acl -Path $Path -AclObject $Acl -ErrorAction Stop + } + + End { + Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer + } +} +#endregion + +#endregion +##*============================================= +##* END FUNCTION LISTINGS +##*============================================= + +##*============================================= +##* SCRIPT BODY +##*============================================= +#region ScriptBody + +## If the script was invoked by the Help Console, exit the script now +If ($invokingScript) { + If ((Split-Path -Path $invokingScript -Leaf) -eq 'AppDeployToolkitHelp.ps1') { Return } +} + +## Add the custom types required for the toolkit +If (-not ([Management.Automation.PSTypeName]'PSADT.UiAutomation').Type) { + [string[]]$ReferencedAssemblies = 'System.Drawing', 'System.Windows.Forms', 'System.DirectoryServices' + Add-Type -Path $appDeployCustomTypesSourceCode -ReferencedAssemblies $ReferencedAssemblies -IgnoreWarnings -ErrorAction 'Stop' +} + +## Define ScriptBlocks to disable/revert script logging +[scriptblock]$DisableScriptLogging = { $OldDisableLoggingValue = $DisableLogging ; $DisableLogging = $true } +[scriptblock]$RevertScriptLogging = { $DisableLogging = $OldDisableLoggingValue } + +## Define ScriptBlock for getting details for all logged on users +[scriptblock]$GetLoggedOnUserDetails = { + [psobject[]]$LoggedOnUserSessions = Get-LoggedOnUser + [string[]]$usersLoggedOn = $LoggedOnUserSessions | ForEach-Object { $_.NTAccount } + + If ($usersLoggedOn) { + # Get account and session details for the logged on user session that the current process is running under. Note that the account used to execute the current process may be different than the account that is logged into the session (i.e. you can use "RunAs" to launch with different credentials when logged into an account). + [psobject]$CurrentLoggedOnUserSession = $LoggedOnUserSessions | Where-Object { $_.IsCurrentSession } + + # Get account and session details for the account running as the console user (user with control of the physical monitor, keyboard, and mouse) + [psobject]$CurrentConsoleUserSession = $LoggedOnUserSessions | Where-Object { $_.IsConsoleSession } + + ## Determine the account that will be used to execute commands in the user session when toolkit is running under the SYSTEM account + # If a console user exists, then that will be the active user session. + # If no console user exists but users are logged in, such as on terminal servers, then the first logged-in non-console user that is either 'Active' or 'Connected' is the active user. + [psobject]$RunAsActiveUser = $LoggedOnUserSessions | Where-Object { $_.IsActiveUserSession } + } +} + +## Define ScriptBlock to test for and attempt to make a service healthy by checking if it exists, is currently running, and has the specified start mode. +[scriptblock]$TestServiceHealth = { + Param ( + [string]$ServiceName, + [string]$ServiceStartMode = 'Automatic' + ) + Try { + [boolean]$IsServiceHealthy = $true + If (Test-ServiceExists -Name $ServiceName -ContinueOnError $false) { + If ((Get-ServiceStartMode -Name $ServiceName -ContinueOnError $false) -ne $ServiceStartMode) { + Set-ServiceStartMode -Name $ServiceName -StartMode $ServiceStartMode -ContinueOnError $false + } + Start-ServiceAndDependencies -Name $ServiceName -SkipServiceExistsTest -ContinueOnError $false + } + Else { + [boolean]$IsServiceHealthy = $false + } + } + Catch { + [boolean]$IsServiceHealthy = $false + } + Write-Output -InputObject $IsServiceHealthy +} + +## Disable logging until log file details are available +. $DisableScriptLogging + +## If the default Deploy-Application.ps1 hasn't been modified, and the main script was not called by a referring script, check for MSI / MST and modify the install accordingly +If ((-not $appName) -and (-not $ReferredInstallName)){ + # Build properly formatted Architecture String + switch ($Is64Bit) { + $false { $formattedOSArch = "x86" } + $true { $formattedOSArch = "x64" } + } + # Find the first MSI file in the Files folder and use that as our install + if ([string]$defaultMsiFile = (Get-ChildItem -LiteralPath $dirFiles -ErrorAction 'SilentlyContinue' | Where-Object { (-not $_.PsIsContainer) -and ([IO.Path]::GetExtension($_.Name) -eq ".msi") -and ($_.Name.EndsWith(".$formattedOSArch.msi")) } | Select-Object -ExpandProperty 'FullName' -First 1)) { + Write-Log -Message "Discovered $formattedOSArch Zerotouch MSI under $defaultMSIFile" -Source $appDeployToolkitName + } + elseif ([string]$defaultMsiFile = (Get-ChildItem -LiteralPath $dirFiles -ErrorAction 'SilentlyContinue' | Where-Object { (-not $_.PsIsContainer) -and ([IO.Path]::GetExtension($_.Name) -eq ".msi") } | Select-Object -ExpandProperty 'FullName' -First 1)) { + Write-Log -Message "Discovered Arch-Independent Zerotouch MSI under $defaultMSIFile" -Source $appDeployToolkitName + } + If ($defaultMsiFile) { + Try { + [boolean]$useDefaultMsi = $true + Write-Log -Message "Discovered Zero-Config MSI installation file [$defaultMsiFile]." -Source $appDeployToolkitName + # Discover if there is a zero-config MST file + [string]$defaultMstFile = [IO.Path]::ChangeExtension($defaultMsiFile, 'mst') + If (Test-Path -LiteralPath $defaultMstFile -PathType 'Leaf') { + Write-Log -Message "Discovered Zero-Config MST installation file [$defaultMstFile]." -Source $appDeployToolkitName + } + Else { + [string]$defaultMstFile = '' + } + # Discover if there are zero-config MSP files. Name multiple MSP files in alphabetical order to control order in which they are installed. + [string[]]$defaultMspFiles = Get-ChildItem -LiteralPath $dirFiles -ErrorAction 'SilentlyContinue' | Where-Object { (-not $_.PsIsContainer) -and ([IO.Path]::GetExtension($_.Name) -eq '.msp') } | Select-Object -ExpandProperty 'FullName' + If ($defaultMspFiles) { + Write-Log -Message "Discovered Zero-Config MSP installation file(s) [$($defaultMspFiles -join ',')]." -Source $appDeployToolkitName + } + + ## Read the MSI and get the installation details + [hashtable]$GetDefaultMsiTablePropertySplat = @{ Path = $defaultMsiFile; Table = 'Property'; ContinueOnError = $false; ErrorAction = 'Stop' } + If ($defaultMstFile) { $GetDefaultMsiTablePropertySplat.Add('TransformPath', $defaultMstFile) } + [psobject]$defaultMsiPropertyList = Get-MsiTableProperty @GetDefaultMsiTablePropertySplat + [string]$appVendor = $defaultMsiPropertyList.Manufacturer + [string]$appName = $defaultMsiPropertyList.ProductName + [string]$appVersion = $defaultMsiPropertyList.ProductVersion + $GetDefaultMsiTablePropertySplat.Set_Item('Table', 'File') + [psobject]$defaultMsiFileList = Get-MsiTableProperty @GetDefaultMsiTablePropertySplat + [string[]]$defaultMsiExecutables = Get-Member -InputObject $defaultMsiFileList -ErrorAction 'Stop' | Select-Object -ExpandProperty 'Name' -ErrorAction 'Stop' | Where-Object { [IO.Path]::GetExtension($_) -eq '.exe' } | ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($_) } + [string]$defaultMsiExecutablesList = $defaultMsiExecutables -join ',' + Write-Log -Message "App Vendor [$appVendor]." -Source $appDeployToolkitName + Write-Log -Message "App Name [$appName]." -Source $appDeployToolkitName + Write-Log -Message "App Version [$appVersion]." -Source $appDeployToolkitName + Write-Log -Message "MSI Executable List [$defaultMsiExecutablesList]." -Source $appDeployToolkitName + } + Catch { + Write-Log -Message "Failed to process Zero-Config MSI Deployment. `n$(Resolve-Error)" -Source $appDeployToolkitName + $useDefaultMsi = $false ; $appVendor = '' ; $appName = '' ; $appVersion = '' + } + } +} + +## Set up sample variables if Dot Sourcing the script, app details have not been specified +If (-not $appName) { + [string]$appName = $appDeployMainScriptFriendlyName + If (-not $appVendor) { [string]$appVendor = 'PS' } + If (-not $appVersion) { [string]$appVersion = $appDeployMainScriptVersion } + If (-not $appLang) { [string]$appLang = $currentLanguage } + If (-not $appRevision) { [string]$appRevision = '01' } + If (-not $appArch) { [string]$appArch = '' } +} + +## Sanitize the application details, as they can cause issues in the script +[string]$appVendor = (Remove-InvalidFileNameChars -Name ($appVendor.Trim())) +[string]$appName = (Remove-InvalidFileNameChars -Name ($appName.Trim())) +[string]$appVersion = (Remove-InvalidFileNameChars -Name ($appVersion.Trim())) +[string]$appArch = (Remove-InvalidFileNameChars -Name ($appArch.Trim())) +[string]$appLang = (Remove-InvalidFileNameChars -Name ($appLang.Trim())) +[string]$appRevision = (Remove-InvalidFileNameChars -Name ($appRevision.Trim())) + +## Build the Installation Title +If ($ReferredInstallTitle) { [string]$installTitle = (Remove-InvalidFileNameChars -Name ($ReferredInstallTitle.Trim())) } +If (-not $installTitle) { + [string]$installTitle = "$appVendor $appName $appVersion" +} + +## Set Powershell window title, in case the window is visible +[string]$oldPSWindowTitle = $Host.UI.RawUI.WindowTitle +$Host.UI.RawUI.WindowTitle = "$installTitle - $DeploymentType" + +## Build the Installation Name +If ($ReferredInstallName) { [string]$installName = (Remove-InvalidFileNameChars -Name $ReferredInstallName) } +If (-not $installName) { + If ($appArch) { + [string]$installName = $appVendor + '_' + $appName + '_' + $appVersion + '_' + $appArch + '_' + $appLang + '_' + $appRevision + } + Else { + [string]$installName = $appVendor + '_' + $appName + '_' + $appVersion + '_' + $appLang + '_' + $appRevision + } +} +[string]$installName = (($installName -replace ' ','').Trim('_') -replace '[_]+','_') + +## Set the Defer History registry path +[string]$regKeyDeferHistory = "$configToolkitRegPath\$appDeployToolkitName\DeferHistory\$installName" + +## Variables: Log Files +If ($ReferredLogName) { [string]$logName = $ReferredLogName } +If (-not $logName) { [string]$logName = $installName + '_' + $appDeployToolkitName + '_' + $deploymentType + '.log' } +# If option to compress logs is selected, then log will be created in temp log folder ($logTempFolder) and then copied to actual log folder ($configToolkitLogDir) after being zipped. +[string]$logTempFolder = Join-Path -Path $envTemp -ChildPath "${installName}_$deploymentType" +If ($configToolkitCompressLogs) { + # If the temp log folder already exists from a previous ZIP operation, then delete all files in it to avoid issues + If (Test-Path -LiteralPath $logTempFolder -PathType 'Container' -ErrorAction 'SilentlyContinue') { + $null = Remove-Item -LiteralPath $logTempFolder -Recurse -Force -ErrorAction 'SilentlyContinue' + } +} + +## Revert script logging to original setting +. $RevertScriptLogging + +## Initialize Logging +$installPhase = 'Initialization' +$scriptSeparator = '*' * 79 +Write-Log -Message ($scriptSeparator,$scriptSeparator) -Source $appDeployToolkitName +Write-Log -Message "[$installName] setup started." -Source $appDeployToolkitName + +## Assemblies: Load +Try { + Add-Type -AssemblyName 'System.Windows.Forms' -ErrorAction 'Stop' + Add-Type -AssemblyName 'PresentationFramework' -ErrorAction 'Stop' + Add-Type -AssemblyName 'Microsoft.VisualBasic' -ErrorAction 'Stop' + Add-Type -AssemblyName 'System.Drawing' -ErrorAction 'Stop' + Add-Type -AssemblyName 'PresentationCore' -ErrorAction 'Stop' + Add-Type -AssemblyName 'WindowsBase' -ErrorAction 'Stop' +} +Catch { + Write-Log -Message "Failed to load assembly. `n$(Resolve-Error)" -Severity 3 -Source $appDeployToolkitName + If ($deployModeNonInteractive) { + Write-Log -Message "Continue despite assembly load error since deployment mode is [$deployMode]." -Source $appDeployToolkitName + } + Else { + Exit-Script -ExitCode 60004 + } +} + +## Check how the script was invoked +If ($invokingScript) { + Write-Log -Message "Script [$scriptPath] dot-source invoked by [$invokingScript]" -Source $appDeployToolkitName +} +Else { + Write-Log -Message "Script [$scriptPath] invoked directly" -Source $appDeployToolkitName +} + +## Dot Source script extensions +If (Test-Path -LiteralPath "$scriptRoot\$appDeployToolkitDotSourceExtensions" -PathType 'Leaf') { + . "$scriptRoot\$appDeployToolkitDotSourceExtensions" +} + +## Evaluate non-default parameters passed to the scripts +If ($deployAppScriptParameters) { [string]$deployAppScriptParameters = ($deployAppScriptParameters.GetEnumerator() | ForEach-Object { If ($_.Value.GetType().Name -eq 'SwitchParameter') { "-$($_.Key):`$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Boolean') { "-$($_.Key) `$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Int32') { "-$($_.Key) $($_.Value)" } Else { "-$($_.Key) `"$($_.Value)`"" } }) -join ' ' } +# Save main script parameters hashtable for async execution of the toolkit +[hashtable]$appDeployMainScriptAsyncParameters = $appDeployMainScriptParameters +If ($appDeployMainScriptParameters) { [string]$appDeployMainScriptParameters = ($appDeployMainScriptParameters.GetEnumerator() | ForEach-Object { If ($_.Value.GetType().Name -eq 'SwitchParameter') { "-$($_.Key):`$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Boolean') { "-$($_.Key) `$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Int32') { "-$($_.Key) $($_.Value)" } Else { "-$($_.Key) `"$($_.Value)`"" } }) -join ' ' } +If ($appDeployExtScriptParameters) { [string]$appDeployExtScriptParameters = ($appDeployExtScriptParameters.GetEnumerator() | ForEach-Object { If ($_.Value.GetType().Name -eq 'SwitchParameter') { "-$($_.Key):`$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Boolean') { "-$($_.Key) `$" + "$($_.Value)".ToLower() } ElseIf ($_.Value.GetType().Name -eq 'Int32') { "-$($_.Key) $($_.Value)" } Else { "-$($_.Key) `"$($_.Value)`"" } }) -join ' ' } + +## Check the XML config file version +If ($configConfigVersion -lt $appDeployMainScriptMinimumConfigVersion) { + [string]$XMLConfigVersionErr = "The XML configuration file version [$configConfigVersion] is lower than the supported version required by the Toolkit [$appDeployMainScriptMinimumConfigVersion]. Please upgrade the configuration file." + Write-Log -Message $XMLConfigVersionErr -Severity 3 -Source $appDeployToolkitName + Throw $XMLConfigVersionErr +} + +## Log system/script information +If ($appScriptVersion) { Write-Log -Message "[$installName] script version is [$appScriptVersion]" -Source $appDeployToolkitName } +If ($appScriptDate) { Write-Log -Message "[$installName] script date is [$appScriptDate]" -Source $appDeployToolkitName } +If ($appScriptAuthor) { Write-Log -Message "[$installName] script author is [$appScriptAuthor]" -Source $appDeployToolkitName } +If ($deployAppScriptFriendlyName) { Write-Log -Message "[$deployAppScriptFriendlyName] script version is [$deployAppScriptVersion]" -Source $appDeployToolkitName } +If ($deployAppScriptParameters) { Write-Log -Message "The following non-default parameters were passed to [$deployAppScriptFriendlyName]: [$deployAppScriptParameters]" -Source $appDeployToolkitName } +If ($appDeployMainScriptFriendlyName) { Write-Log -Message "[$appDeployMainScriptFriendlyName] script version is [$appDeployMainScriptVersion]" -Source $appDeployToolkitName } +If ($appDeployMainScriptParameters) { Write-Log -Message "The following non-default parameters were passed to [$appDeployMainScriptFriendlyName]: [$appDeployMainScriptParameters]" -Source $appDeployToolkitName } +If ($appDeployExtScriptFriendlyName) { Write-Log -Message "[$appDeployExtScriptFriendlyName] version is [$appDeployExtScriptVersion]" -Source $appDeployToolkitName } +If ($appDeployExtScriptParameters) { Write-Log -Message "The following non-default parameters were passed to [$appDeployExtScriptFriendlyName]: [$appDeployExtScriptParameters]" -Source $appDeployToolkitName } +Write-Log -Message "Computer Name is [$envComputerNameFQDN]" -Source $appDeployToolkitName +Write-Log -Message "Current User is [$ProcessNTAccount]" -Source $appDeployToolkitName +If ($envOSServicePack) { + Write-Log -Message "OS Version is [$envOSName $envOSServicePack $envOSArchitecture $envOSVersion]" -Source $appDeployToolkitName +} +Else { + Write-Log -Message "OS Version is [$envOSName $envOSArchitecture $envOSVersion]" -Source $appDeployToolkitName +} +Write-Log -Message "OS Type is [$envOSProductTypeName]" -Source $appDeployToolkitName +Write-Log -Message "Current Culture is [$($culture.Name)], language is [$currentLanguage] and UI language is [$currentUILanguage]" -Source $appDeployToolkitName +Write-Log -Message "Hardware Platform is [$(. $DisableScriptLogging; Get-HardwarePlatform; . $RevertScriptLogging)]" -Source $appDeployToolkitName +Write-Log -Message "PowerShell Host is [$($envHost.Name)] with version [$($envHost.Version)]" -Source $appDeployToolkitName +Write-Log -Message "PowerShell Version is [$envPSVersion $psArchitecture]" -Source $appDeployToolkitName +Write-Log -Message "PowerShell CLR (.NET) version is [$envCLRVersion]" -Source $appDeployToolkitName +Write-Log -Message $scriptSeparator -Source $appDeployToolkitName + +## Disable logging +. $DisableScriptLogging + +## Dot source ScriptBlock to get a list of all users logged on to the system (both local and RDP users), and discover session details for account executing script +. $GetLoggedOnUserDetails + +## Dot source ScriptBlock to load localized UI messages from config XML +. $xmlLoadLocalizedUIMessages + +## Dot source ScriptBlock to get system DPI scale factor +. $GetDisplayScaleFactor + +## Revert script logging to original setting +. $RevertScriptLogging + +## Set the install phase to asynchronous if the script was not dot sourced, i.e. called with parameters +If ($AsyncToolkitLaunch) { + $installPhase = 'Asynchronous' +} + +## If the ShowInstallationPrompt Parameter is specified, only call that function. +If ($showInstallationPrompt) { + Write-Log -Message "[$appDeployMainScriptFriendlyName] called with switch [-ShowInstallationPrompt]." -Source $appDeployToolkitName + $appDeployMainScriptAsyncParameters.Remove('ShowInstallationPrompt') + $appDeployMainScriptAsyncParameters.Remove('AsyncToolkitLaunch') + $appDeployMainScriptAsyncParameters.Remove('ReferredInstallName') + $appDeployMainScriptAsyncParameters.Remove('ReferredInstallTitle') + $appDeployMainScriptAsyncParameters.Remove('ReferredLogName') + Show-InstallationPrompt @appDeployMainScriptAsyncParameters + Exit 0 +} + +## If the ShowInstallationRestartPrompt Parameter is specified, only call that function. +If ($showInstallationRestartPrompt) { + Write-Log -Message "[$appDeployMainScriptFriendlyName] called with switch [-ShowInstallationRestartPrompt]." -Source $appDeployToolkitName + $appDeployMainScriptAsyncParameters.Remove('ShowInstallationRestartPrompt') + $appDeployMainScriptAsyncParameters.Remove('AsyncToolkitLaunch') + $appDeployMainScriptAsyncParameters.Remove('ReferredInstallName') + $appDeployMainScriptAsyncParameters.Remove('ReferredInstallTitle') + $appDeployMainScriptAsyncParameters.Remove('ReferredLogName') + Show-InstallationRestartPrompt @appDeployMainScriptAsyncParameters + Exit 0 +} + +## If the CleanupBlockedApps Parameter is specified, only call that function. +If ($cleanupBlockedApps) { + $deployModeSilent = $true + Write-Log -Message "[$appDeployMainScriptFriendlyName] called with switch [-CleanupBlockedApps]." -Source $appDeployToolkitName + Unblock-AppExecution + Exit 0 +} + +## If the ShowBlockedAppDialog Parameter is specified, only call that function. +If ($showBlockedAppDialog) { + Try { + . $DisableScriptLogging + Write-Log -Message "[$appDeployMainScriptFriendlyName] called with switch [-ShowBlockedAppDialog]." -Source $appDeployToolkitName + # Create a mutex and specify a name without acquiring a lock on the mutex + [boolean]$showBlockedAppDialogMutexLocked = $false + [string]$showBlockedAppDialogMutexName = 'Global\PSADT_ShowBlockedAppDialog_Message' + [Threading.Mutex]$showBlockedAppDialogMutex = New-Object -TypeName 'System.Threading.Mutex' -ArgumentList ($false, $showBlockedAppDialogMutexName) + # Attempt to acquire an exclusive lock on the mutex, attempt will fail after 1 millisecond if unable to acquire exclusive lock + If ((Test-IsMutexAvailable -MutexName $showBlockedAppDialogMutexName -MutexWaitTimeInMilliseconds 1) -and ($showBlockedAppDialogMutex.WaitOne(1))) { + [boolean]$showBlockedAppDialogMutexLocked = $true + Show-InstallationPrompt -Title $installTitle -Message $configBlockExecutionMessage -Icon 'Warning' -ButtonRightText 'OK' + Exit 0 + } + Else { + # If attempt to acquire an exclusive lock on the mutex failed, then exit script as another blocked app dialog window is already open + Write-Log -Message "Unable to acquire an exclusive lock on mutex [$showBlockedAppDialogMutexName] because another blocked application dialog window is already open. Exiting script..." -Severity 2 -Source $appDeployToolkitName + Exit 0 + } + } + Catch { + Write-Log -Message "There was an error in displaying the Installation Prompt. `n$(Resolve-Error)" -Severity 3 -Source $appDeployToolkitName + Exit 60005 + } + Finally { + If ($showBlockedAppDialogMutexLocked) { $null = $showBlockedAppDialogMutex.ReleaseMutex() } + If ($showBlockedAppDialogMutex) { $showBlockedAppDialogMutex.Close() } + } +} + +## Log details for all currently logged in users +Write-Log -Message "Display session information for all logged on users: `n$($LoggedOnUserSessions | Format-List | Out-String)" -Source $appDeployToolkitName +If ($usersLoggedOn) { + Write-Log -Message "The following users are logged on to the system: [$($usersLoggedOn -join ', ')]." -Source $appDeployToolkitName + + # Check if the current process is running in the context of one of the logged in users + If ($CurrentLoggedOnUserSession) { + Write-Log -Message "Current process is running with user account [$ProcessNTAccount] under logged in user session for [$($CurrentLoggedOnUserSession.NTAccount)]." -Source $appDeployToolkitName + } + Else { + Write-Log -Message "Current process is running under a system account [$ProcessNTAccount]." -Source $appDeployToolkitName + } + + # Display account and session details for the account running as the console user (user with control of the physical monitor, keyboard, and mouse) + If ($CurrentConsoleUserSession) { + Write-Log -Message "The following user is the console user [$($CurrentConsoleUserSession.NTAccount)] (user with control of physical monitor, keyboard, and mouse)." -Source $appDeployToolkitName + } + Else { + Write-Log -Message 'There is no console user logged in (user with control of physical monitor, keyboard, and mouse).' -Source $appDeployToolkitName + } + + # Display the account that will be used to execute commands in the user session when toolkit is running under the SYSTEM account + If ($RunAsActiveUser) { + Write-Log -Message "The active logged on user is [$($RunAsActiveUser.NTAccount)]." -Source $appDeployToolkitName + } +} +Else { + Write-Log -Message 'No users are logged on to the system.' -Source $appDeployToolkitName +} + +## Log which language's UI messages are loaded from the config XML file +If ($HKUPrimaryLanguageShort) { + Write-Log -Message "The active logged on user [$($RunAsActiveUser.NTAccount)] has a primary UI language of [$HKUPrimaryLanguageShort]." -Source $appDeployToolkitName +} +Else { + Write-Log -Message "The current system account [$ProcessNTAccount] has a primary UI language of [$currentLanguage]." -Source $appDeployToolkitName +} +If ($configInstallationUILanguageOverride) { Write-Log -Message "The config XML file was configured to override the detected primary UI language with the following UI language: [$configInstallationUILanguageOverride]." -Source $appDeployToolkitName } +Write-Log -Message "The following UI messages were imported from the config XML file: [$xmlUIMessageLanguage]." -Source $appDeployToolkitName + +## Log system DPI scale factor of active logged on user +If ($UserDisplayScaleFactor) { + Write-Log -Message "The active logged on user [$($RunAsActiveUser.NTAccount)] has a DPI scale factor of [$dpiScale] with DPI pixels [$dpiPixels]." -Source $appDeployToolkitName +} +Else { + Write-Log -Message "The system has a DPI scale factor of [$dpiScale] with DPI pixels [$dpiPixels]." -Source $appDeployToolkitName +} + +## Check if script is running from a SCCM Task Sequence +Try { + [__comobject]$SMSTSEnvironment = New-Object -ComObject 'Microsoft.SMS.TSEnvironment' -ErrorAction 'Stop' + Write-Log -Message 'Successfully loaded COM Object [Microsoft.SMS.TSEnvironment]. Therefore, script is currently running from a SCCM Task Sequence.' -Source $appDeployToolkitName + $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($SMSTSEnvironment) + $runningTaskSequence = $true +} +Catch { + Write-Log -Message 'Unable to load COM Object [Microsoft.SMS.TSEnvironment]. Therefore, script is not currently running from a SCCM Task Sequence.' -Source $appDeployToolkitName + $runningTaskSequence = $false +} + + +## Check to see if the Task Scheduler service is in a healthy state by checking its services to see if they exist, are currently running, and have a start mode of 'Automatic'. +## The task scheduler service and the services it is dependent on can/should only be started/stopped/modified when running in the SYSTEM context. +[boolean]$IsTaskSchedulerHealthy = $true +If ($IsLocalSystemAccount) { + # Check the health of the 'COM+ Event System' service + [boolean]$IsTaskSchedulerHealthy = & $TestServiceHealth -ServiceName 'EventSystem' + # Check the health of the 'Remote Procedure Call (RPC)' service + [boolean]$IsTaskSchedulerHealthy = & $TestServiceHealth -ServiceName 'RpcSs' + # Check the health of the 'Windows Event Log' service + [boolean]$IsTaskSchedulerHealthy = & $TestServiceHealth -ServiceName 'EventLog' + # Check the health of the 'Task Scheduler' service + [boolean]$IsTaskSchedulerHealthy = & $TestServiceHealth -ServiceName 'Schedule' + + Write-Log -Message "The task scheduler service is in a healthy state: $IsTaskSchedulerHealthy." -Source $appDeployToolkitName +} +Else { + Write-Log -Message "Skipping attempt to check for and make the task scheduler services healthy because the App Deployment Toolkit is not running under the [$LocalSystemNTAccount] account." -Source $appDeployToolkitName +} + +## If script is running in session zero +If ($SessionZero) { + ## If the script was launched with deployment mode set to NonInteractive, then continue + If ($deployMode -eq 'NonInteractive') { + Write-Log -Message "Session 0 detected but deployment mode was manually set to [$deployMode]." -Source $appDeployToolkitName + } + Else { + ## If the process is not able to display a UI, enable NonInteractive mode + If (-not $IsProcessUserInteractive) { + $deployMode = 'NonInteractive' + Write-Log -Message "Session 0 detected, process not running in user interactive mode; deployment mode set to [$deployMode]." -Source $appDeployToolkitName + } + Else { + If (-not $usersLoggedOn) { + $deployMode = 'NonInteractive' + Write-Log -Message "Session 0 detected, process running in user interactive mode, no users logged in; deployment mode set to [$deployMode]." -Source $appDeployToolkitName + } + Else { + Write-Log -Message 'Session 0 detected, process running in user interactive mode, user(s) logged in.' -Source $appDeployToolkitName + } + } + } +} +Else { + Write-Log -Message 'Session 0 not detected.' -Source $appDeployToolkitName +} + +## Set Deploy Mode switches +If ($deployMode) { + Write-Log -Message "Installation is running in [$deployMode] mode." -Source $appDeployToolkitName +} +Switch ($deployMode) { + 'Silent' { $deployModeSilent = $true } + 'NonInteractive' { $deployModeNonInteractive = $true; $deployModeSilent = $true } + Default { $deployModeNonInteractive = $false; $deployModeSilent = $false } +} + +## Check deployment type (install/uninstall) +Switch ($deploymentType) { + 'Install' { $deploymentTypeName = $configDeploymentTypeInstall } + 'Uninstall' { $deploymentTypeName = $configDeploymentTypeUnInstall } + 'Repair' { $deploymentTypeName = $configDeploymentTypeRepair } + Default { $deploymentTypeName = $configDeploymentTypeInstall } +} +If ($deploymentTypeName) { Write-Log -Message "Deployment type is [$deploymentTypeName]." -Source $appDeployToolkitName } + +If ($useDefaultMsi) { Write-Log -Message "Discovered Zero-Config MSI installation file [$defaultMsiFile]." -Source $appDeployToolkitName } + +## Check current permissions and exit if not running with Administrator rights +If ($configToolkitRequireAdmin) { + # Check if the current process is running with elevated administrator permissions + If ((-not $IsAdmin) -and (-not $ShowBlockedAppDialog)) { + [string]$AdminPermissionErr = "[$appDeployToolkitName] has an XML config file option [Toolkit_RequireAdmin] set to [True] so as to require Administrator rights for the toolkit to function. Please re-run the deployment script as an Administrator or change the option in the XML config file to not require Administrator rights." + Write-Log -Message $AdminPermissionErr -Severity 3 -Source $appDeployToolkitName + Show-DialogBox -Text $AdminPermissionErr -Icon 'Stop' + Throw $AdminPermissionErr + } +} + +## If terminal server mode was specified, change the installation mode to support it +If ($terminalServerMode) { Enable-TerminalServerInstallMode } + +#endregion +##*============================================= +##* END SCRIPT BODY +##*============================================= diff --git a/Calibry/5.0/PS_Deployment/Deploy-Application.exe b/Calibry/5.0/PS_Deployment/Deploy-Application.exe new file mode 100644 index 0000000..d72824d Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Deploy-Application.exe differ diff --git a/Calibry/5.0/PS_Deployment/Deploy-Application.exe.config b/Calibry/5.0/PS_Deployment/Deploy-Application.exe.config new file mode 100644 index 0000000..e6a6e70 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Deploy-Application.exe.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Calibry/5.0/PS_Deployment/Deploy-Application.ps1 b/Calibry/5.0/PS_Deployment/Deploy-Application.ps1 new file mode 100644 index 0000000..74f8600 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Deploy-Application.ps1 @@ -0,0 +1,255 @@ +<# +.SYNOPSIS + This script performs the installation or uninstallation of an application(s). + # LICENSE # + PowerShell App Deployment Toolkit - Provides a set of functions to perform common application deployment tasks on Windows. + Copyright (C) 2017 - Sean Lillis, Dan Cunningham, Muhammad Mashwani, Aman Motazedian. + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . +.DESCRIPTION + The script is provided as a template to perform an install or uninstall of an application(s). + The script either performs an "Install" deployment type or an "Uninstall" deployment type. + The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install. + The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application. +.PARAMETER DeploymentType + The type of deployment to perform. Default is: Install. +.PARAMETER DeployMode + Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive. +.PARAMETER AllowRebootPassThru + Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered. +.PARAMETER TerminalServerMode + Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers. +.PARAMETER DisableLogging + Disables logging to file for the script. Default is: $false. +.EXAMPLE + powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeployMode 'Silent'; Exit $LastExitCode }" +.EXAMPLE + powershell.exe -Command "& { & '.\Deploy-Application.ps1' -AllowRebootPassThru; Exit $LastExitCode }" +.EXAMPLE + powershell.exe -Command "& { & '.\Deploy-Application.ps1' -DeploymentType 'Uninstall'; Exit $LastExitCode }" +.EXAMPLE + Deploy-Application.exe -DeploymentType "Install" -DeployMode "Silent" +.NOTES + Toolkit Exit Code Ranges: + 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 + 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 + 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 +.LINK + http://psappdeploytoolkit.com +#> +[CmdletBinding()] +Param ( + [Parameter(Mandatory=$false)] + [ValidateSet('Install','Uninstall','Repair')] + [string]$DeploymentType = 'Install', + [Parameter(Mandatory=$false)] + [ValidateSet('Interactive','Silent','NonInteractive')] + [string]$DeployMode = 'Interactive', + [Parameter(Mandatory=$false)] + [switch]$AllowRebootPassThru = $false, + [Parameter(Mandatory=$false)] + [switch]$TerminalServerMode = $false, + [Parameter(Mandatory=$false)] + [switch]$DisableLogging = $false +) + +Try { + ## Set the script execution policy for this process + Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {} + + ##*=============================================== + ##* VARIABLE DECLARATION + ##*=============================================== + ## Variables: Application + [string]$appVendor = 'Metler Toledo' + [string]$appName = 'Calibry' + [string]$appVersion = '5.0' + [string]$appArch = 'x86' + [string]$appLang = 'EN' + [string]$appRevision = '01' + [string]$appScriptVersion = '1.0.0' + [string]$appScriptDate = '03/26/2021' + [string]$appScriptAuthor = 'John Palmer' + ##*=============================================== + ## Variables: Install Titles (Only set here to override defaults set by the toolkit) + [string]$installName = '' + [string]$installTitle = '' + + ##* Do not modify section below + #region DoNotModify + + ## Variables: Exit Code + [int32]$mainExitCode = 0 + + ## Variables: Script + [string]$deployAppScriptFriendlyName = 'Deploy Application' + [version]$deployAppScriptVersion = [version]'3.8.3' + [string]$deployAppScriptDate = '30/09/2020' + [hashtable]$deployAppScriptParameters = $psBoundParameters + + ## Variables: Environment + If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation } + [string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent + + ## Dot source the required App Deploy Toolkit Functions + Try { + [string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1" + If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." } + If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain } + } + Catch { + If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 } + Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue' + ## Exit the script, returning the exit code to SCCM + If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode } + } + + #endregion + ##* Do not modify section above + ##*=============================================== + ##* END VARIABLE DECLARATION + ##*=============================================== + + If ($deploymentType -ine 'Uninstall' -and $deploymentType -ine 'Repair') { + ##*=============================================== + ##* PRE-INSTALLATION + ##*=============================================== + [string]$installPhase = 'Pre-Installation' + + ## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt + #Show-InstallationWelcome -CloseApps 'iexplore' -AllowDefer -DeferTimes 3 -CheckDiskSpace -PersistPrompt + + ## Show Progress Message (with the default message) + #Show-InstallationProgress + + ## + + + ##*=============================================== + ##* INSTALLATION + ##*=============================================== + [string]$installPhase = 'Installation' + + ## Handle Zero-Config MSI Installations + If ($useDefaultMsi) { + [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Install'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } + Execute-MSI @ExecuteDefaultMSISplat; If ($defaultMspFiles) { $defaultMspFiles | ForEach-Object { Execute-MSI -Action 'Patch' -Path $_ } } + } + + ## + + #Execute-Process -Path 'Install.exe' -Parameters '/S /v/qn' + + #Execute-Process -Path "$dirFiles\Calibry\Calibry\InstallationUI.exe" -Parameters '/S /v/qn' + + Execute-Process -Path "$dirFiles\Calibry\Calibry\InstallationUI.exe" + + #Execute-Process -Path "$dirFiles\Calibry\setup.exe" -Wait + + #Execute-MSI -Action Install -Path 'CalibryInstaller.msi'# -Parameters #'/q' + + ##*=============================================== + ##* POST-INSTALLATION + ##*=============================================== + [string]$installPhase = 'Post-Installation' + + ## + + Execute-Process -Path "$dirFiles\Patch for calibry.exe" + + Copy-Item -Path "$dirFiles\Configuration.xml" -Destination "C:\ProgramData\METTLER TOLEDO\Calibry" -Recurse + + #Execute-MSI -Action Install -Path $dirFiles\3f8a8cf7.msi -Parameters '/q' + + ## Display a message at the end of the install + If (-not $useDefaultMsi) { #Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install or remove it completely for unattended installations.' -ButtonRightText 'OK' -Icon Information -NoWait } + } + } + ElseIf ($deploymentType -ieq 'Uninstall') + { + ##*=============================================== + ##* PRE-UNINSTALLATION + ##*=============================================== + [string]$installPhase = 'Pre-Uninstallation' + + ## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing + Show-InstallationWelcome -CloseApps 'iexplore' -CloseAppsCountdown 60 + + ## Show Progress Message (with the default message) + Show-InstallationProgress + + ## + + + ##*=============================================== + ##* UNINSTALLATION + ##*=============================================== + [string]$installPhase = 'Uninstallation' + + ## Handle Zero-Config MSI Uninstallations + If ($useDefaultMsi) { + [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Uninstall'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } + Execute-MSI @ExecuteDefaultMSISplat + } + + # + + Execute-MSI -Action Uninstall -Path '{F357E98D-6AE9-4FE4-86BE-A2254DEC7F18}' -Parameters '/q /norestart' + + + ##*=============================================== + ##* POST-UNINSTALLATION + ##*=============================================== + [string]$installPhase = 'Post-Uninstallation' + + ## + + + } + ElseIf ($deploymentType -ieq 'Repair') + { + ##*=============================================== + ##* PRE-REPAIR + ##*=============================================== + [string]$installPhase = 'Pre-Repair' + + ## Show Progress Message (with the default message) + Show-InstallationProgress + + ## + + ##*=============================================== + ##* REPAIR + ##*=============================================== + [string]$installPhase = 'Repair' + + ## Handle Zero-Config MSI Repairs + If ($useDefaultMsi) { + [hashtable]$ExecuteDefaultMSISplat = @{ Action = 'Repair'; Path = $defaultMsiFile; }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) } + Execute-MSI @ExecuteDefaultMSISplat + } + # + + ##*=============================================== + ##* POST-REPAIR + ##*=============================================== + [string]$installPhase = 'Post-Repair' + + ## + + + } + ##*=============================================== + ##* END SCRIPT BODY + ##*=============================================== + + ## Call the Exit-Script function to perform final cleanup operations + Exit-Script -ExitCode $mainExitCode +} +Catch { + [int32]$mainExitCode = 60001 + [string]$mainErrorMessage = "$(Resolve-Error)" + Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName + Show-DialogBox -Text $mainErrorMessage -Icon 'Stop' + Exit-Script -ExitCode $mainExitCode +} diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x64.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x64.exe new file mode 100644 index 0000000..f8d0593 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x64.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x86.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x86.exe new file mode 100644 index 0000000..95a8842 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/NDP20SP2-KB981145-x86.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x64.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x64.msu new file mode 100644 index 0000000..790076b Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x64.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x86.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x86.msu new file mode 100644 index 0000000..d5a02db Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.0-KB981145-x86.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x64.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x64.msu new file mode 100644 index 0000000..7668888 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x64.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x86.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x86.msu new file mode 100644 index 0000000..7120c3e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/Windows6.1-KB981145-v2-x86.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/hotfix.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/hotfix.txt new file mode 100644 index 0000000..8ceb4cf --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/MSKB/hotfix.txt @@ -0,0 +1,38 @@ +Hot Fix for Microsoft Knowledge Base article number(s) 981145 + +Knowledge Base Article +====================== +To view the contents of the knowledge base article for this update, visit +http://support.microsoft.com/?kbid=981145 directly, or +visit http://support.microsoft.com and query the Knowledge base +for the product and the KB number of this update. + +Installation Instructions +========================= +1. If this hotfix was delivered with hfx.exe then it can be installed by running +hfx.exe from the appropriate platform directory. + +2. Install and run + +Disclaimer of Liability +====================== +The software which may include "online" or electronic documentation (together the "Software"), +are provided to you at no additional charge. The Software is protected by United States copyright +laws and international copyright treaties, as well as other intellectual property laws and treaties. +Microsoft Corporation owns all right, title and interest to the Software. The Software is licensed, +not sold. + +DISCLAIMER OF WARRANTIES. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, MICROSOFT AND ITS +SUPPLIERS EXPRESSLY DISCLAIM ALL WARRANTIES FOR THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS" +WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +THE ENTIRE RISK ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE REMAINS WITH YOU. + +The Software is provided with RESTRICTED RIGHTS. Use, duplication, or disclosure by the Government +is subject to restrictions set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and +Computer Software clause at DFARS 252.227-7013 or subparagraphs (c)(1) and (2) of the Commercial +Computer Software--Restricted Rights at 48 CFR 52.227-19, as applicable. Manufacturer is Microsoft +Corporation, One Microsoft Way, Redmond, WA 98052-6399. Any transfer of this Software must be +accompanied by this statement and may only be transferred if first approved by Microsoft. + +(c) 2002 Microsoft Corporation, All Rights Reserved. diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/Command line.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/Command line.txt new file mode 100644 index 0000000..43206b2 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/Command line.txt @@ -0,0 +1,27 @@ +Option + Description + +/q + Suppresses all UI. An .ini file cannot be specified with this option. + +/uninstall + Uninstalls product. + +/remove + Same as /uninstall. + +/f + Repairs all .NET Framework components that are installed. + +/nopatch + Specifies that patches are not applied and bypasses patch checking. + +/norollback + Specifies that setup is not rolled back if a setup component fails. + +/norestart + Specifies that the installer does not restart the computer after installation completes. The redistributable installer returns ERROR_SUCCESS_REBOOT_REQUIRED (3010) if a reboot is required. + +/? + Displays this list of options. + diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/dotnetfx35_SP1.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/dotnetfx35_SP1.exe new file mode 100644 index 0000000..fb1fe3d Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/dotnetfx35_SP1.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/error codes.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/error codes.txt new file mode 100644 index 0000000..4c2439c --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/NET35/error codes.txt @@ -0,0 +1,40 @@ +Error code + Value + Description + +ERROR_SUCCESS + 0 + The action completed successfully. + +ERROR_INSTALL_USEREXIT + 1602 + User canceled installation. + +ERROR_INSTALL_FAILURE + 1603 + A fatal error occurred during installation. + +ERROR_UNKNOWN_PRODUCT + 1605 + This action is valid only for products that are currently installed. + +ERROR_PATCH_PACKAGE_INVALID + 1636 + The patch package could not be opened, or the patch was not applicable to the .NET Framework. + +ERROR_INVALID_COMMAND_LINE + 1639 + Invalid command-line argument. + +ERROR_SUCCESS_REBOOT_INITIATED + 1641 + The installer has initiated a restart. This indicates success, and setup will continue after restart. (The reboot is not performed if the /norestart option was specified.) + +ERROR_PATCH_PACKAGE_REJECTED + 1643 + The patch package is not permitted by system policy. + +ERROR_SUCCESS_REBOOT_REQUIRED + 3010 + A restart is required to complete the installation. This message indicates success. + diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Command line.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Command line.txt new file mode 100644 index 0000000..6bd7f5c --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Command line.txt @@ -0,0 +1,13 @@ +Command-Line Options +The Windows Installer redistributable software update packages use the following case-insensitive command-line options. + +Option Description +/norestart Prevents the redistributable package from asking the user to reboot even if it had to replace files that were in use during the installation. +If the update package is invoked with this option, it returns ERROR_SUCCESS_REBOOT_REQUIRED if it had to replace files that were in use. + +If it did not have to replace files that were in use, it returns ERROR_SUCCESS. See the remarks section for additional information on delayed reboots. + +/quiet For use by applications that redistribute the Windows Installer as part of a bootstrapping application. +A user interface (UI) is not presented to the user. The bootstrapping application should check the return code to determine whether a reboot is needed to complete the installation of the Windows Installer. + +/help Displays help on all the available options. diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Instructions.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Instructions.txt new file mode 100644 index 0000000..fa1e5b0 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Instructions.txt @@ -0,0 +1,17 @@ +Instructions +Download the file that is appropriate for your operating system version and platform. + +For Windows Vista, Windows Vista Service Pack 1 and Windows Server 2008: +x86 Platform: Windows6.0-KB942288-v2-x86.msu +x64 Platform: Windows6.0-KB942288-v2-x64.msu +IA64 Platform: Windows6.0-KB942288-v2-ia64.msu + +For Windows XP Service Pack 2 and Windows XP Service Pack 3 (32-bit platforms): +x86 Platform: WindowsXP-KB942288-v3-x86.exe + +For Windows Server 2003 Service Pack 1, Windows Server 2003 Service Pack 2 and Windows XP 64-bit Editions: +x86 Platform: WindowsServer2003-KB942288-v4-x86.exe +x64 Platform: WindowsServer2003-KB942288-v4-x64.exe +IA64 Platform: WindowsServer2003-KB942288-v4-ia64.exe + +For complete information on installing or upgrading Windows installer, including command line options, please see the Windows Installer Start Page on MSDN. diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-ia64.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-ia64.msu new file mode 100644 index 0000000..b09a0c7 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-ia64.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x64.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x64.msu new file mode 100644 index 0000000..1cff71e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x64.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x86.msu b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x86.msu new file mode 100644 index 0000000..fa7c264 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/Windows6.0-KB942288-v2-x86.msu differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-ia64.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-ia64.exe new file mode 100644 index 0000000..81e41ed Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-ia64.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x64.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x64.exe new file mode 100644 index 0000000..783d7b7 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x64.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x86.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x86.exe new file mode 100644 index 0000000..37d8b48 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsServer2003-KB942288-v4-x86.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsXP-KB942288-v3-x86.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsXP-KB942288-v3-x86.exe new file mode 100644 index 0000000..ad31a1e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/WindowsXP-KB942288-v3-x86.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/error codes.txt b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/error codes.txt new file mode 100644 index 0000000..9aa8c65 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/WI45/error codes.txt @@ -0,0 +1,65 @@ +Error code Value Description +------------------------------------- +ERROR_SUCCESS 0 The action completed successfully. +ERROR_INVALID_DATA 13 The data is invalid. +ERROR_INVALID_PARAMETER 87 One of the parameters was invalid. +ERROR_CALL_NOT_IMPLEMENTED 120 This value is returned when a custom action attempts to call a function that cannot be called from custom actions. The function returns the value ERROR_CALL_NOT_IMPLEMENTED. Available beginning with Windows Installer version 3.0. +ERROR_APPHELP_BLOCK 1259 If Windows Installer determines a product may be incompatible with the current operating system, it displays a dialog box informing the user and asking whether to try to install anyway. This error code is returned if the user chooses not to try the installation. +ERROR_INSTALL_SERVICE_FAILURE 1601 The Windows Installer service could not be accessed. Contact your support personnel to verify that the Windows Installer service is properly registered. +ERROR_INSTALL_USEREXIT 1602 The user cancels installation. +ERROR_INSTALL_FAILURE 1603 A fatal error occurred during installation. +ERROR_INSTALL_SUSPEND 1604 Installation suspended, incomplete. +ERROR_UNKNOWN_PRODUCT 1605 This action is only valid for products that are currently installed. +ERROR_UNKNOWN_FEATURE 1606 The feature identifier is not registered. +ERROR_UNKNOWN_COMPONENT 1607 The component identifier is not registered. +ERROR_UNKNOWN_PROPERTY 1608 This is an unknown property. +ERROR_INVALID_HANDLE_STATE 1609 The handle is in an invalid state. +ERROR_BAD_CONFIGURATION 1610 The configuration data for this product is corrupt. Contact your support personnel. +ERROR_INDEX_ABSENT 1611 The component qualifier not present. +ERROR_INSTALL_SOURCE_ABSENT 1612 The installation source for this product is not available. Verify that the source exists and that you can access it. +ERROR_INSTALL_PACKAGE_VERSION 1613 This installation package cannot be installed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. +ERROR_PRODUCT_UNINSTALLED 1614 The product is uninstalled. +ERROR_BAD_QUERY_SYNTAX 1615 The SQL query syntax is invalid or unsupported. +ERROR_INVALID_FIELD 1616 The record field does not exist. +ERROR_INSTALL_ALREADY_RUNNING 1618 Another installation is already in progress. Complete that installation before proceeding with this install. +For information about the mutex, see _MSIExecute Mutex. + +ERROR_INSTALL_PACKAGE_OPEN_FAILED 1619 This installation package could not be opened. Verify that the package exists and is accessible, or contact the application vendor to verify that this is a valid Windows Installer package. +ERROR_INSTALL_PACKAGE_INVALID 1620 This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer package. +ERROR_INSTALL_UI_FAILURE 1621 There was an error starting the Windows Installer service user interface. Contact your support personnel. +ERROR_INSTALL_LOG_FAILURE 1622 There was an error opening installation log file. Verify that the specified log file location exists and is writable. +ERROR_INSTALL_LANGUAGE_UNSUPPORTED 1623 This language of this installation package is not supported by your system. +ERROR_INSTALL_TRANSFORM_FAILURE 1624 There was an error applying transforms. Verify that the specified transform paths are valid. +ERROR_INSTALL_PACKAGE_REJECTED 1625 This installation is forbidden by system policy. Contact your system administrator. +ERROR_FUNCTION_NOT_CALLED 1626 The function could not be executed. +ERROR_FUNCTION_FAILED 1627 The function failed during execution. +ERROR_INVALID_TABLE 1628 An invalid or unknown table was specified. +ERROR_DATATYPE_MISMATCH 1629 The data supplied is the wrong type. +ERROR_UNSUPPORTED_TYPE 1630 Data of this type is not supported. +ERROR_CREATE_FAILED 1631 The Windows Installer service failed to start. Contact your support personnel. +ERROR_INSTALL_TEMP_UNWRITABLE 1632 The Temp folder is either full or inaccessible. Verify that the Temp folder exists and that you can write to it. +ERROR_INSTALL_PLATFORM_UNSUPPORTED 1633 This installation package is not supported on this platform. Contact your application vendor. +ERROR_INSTALL_NOTUSED 1634 Component is not used on this machine. +ERROR_PATCH_PACKAGE_OPEN_FAILED 1635 This patch package could not be opened. Verify that the patch package exists and is accessible, or contact the application vendor to verify that this is a valid Windows Installer patch package. +ERROR_PATCH_PACKAGE_INVALID 1636 This patch package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer patch package. +ERROR_PATCH_PACKAGE_UNSUPPORTED 1637 This patch package cannot be processed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. +ERROR_PRODUCT_VERSION 1638 Another version of this product is already installed. Installation of this version cannot continue. To configure or remove the existing version of this product, use Add/Remove Programs in Control Panel. +ERROR_INVALID_COMMAND_LINE 1639 Invalid command line argument. Consult the Windows Installer SDK for detailed command-line help. +ERROR_INSTALL_REMOTE_DISALLOWED 1640 The current user is not permitted to perform installations from a client session of a server running the Terminal Server role service. +ERROR_SUCCESS_REBOOT_INITIATED 1641 The installer has initiated a restart. This message is indicative of a success. +ERROR_PATCH_TARGET_NOT_FOUND 1642 The installer cannot install the upgrade patch because the program being upgraded may be missing or the upgrade patch updates a different version of the program. Verify that the program to be upgraded exists on your computer and that you have the correct upgrade patch. +ERROR_PATCH_PACKAGE_REJECTED 1643 The patch package is not permitted by system policy. +ERROR_INSTALL_TRANSFORM_REJECTED 1644 One or more customizations are not permitted by system policy. +ERROR_INSTALL_REMOTE_PROHIBITED 1645 Windows Installer does not permit installation from a Remote Desktop Connection. +ERROR_PATCH_REMOVAL_UNSUPPORTED 1646 The patch package is not a removable patch package. Available beginning with Windows Installer version 3.0. +ERROR_UNKNOWN_PATCH 1647 The patch is not applied to this product. Available beginning with Windows Installer version 3.0. +ERROR_PATCH_NO_SEQUENCE 1648 No valid sequence could be found for the set of patches. Available beginning with Windows Installer version 3.0. +ERROR_PATCH_REMOVAL_DISALLOWED 1649 Patch removal was disallowed by policy. Available beginning with Windows Installer version 3.0. +ERROR_INVALID_PATCH_XML 1650 The XML patch data is invalid. Available beginning with Windows Installer version 3.0. +ERROR_PATCH_MANAGED_ADVERTISED_PRODUCT 1651 Administrative user failed to apply patch for a per-user managed or a per-machine application that is in advertise state. Available beginning with Windows Installer version 3.0. +ERROR_INSTALL_SERVICE_SAFEBOOT 1652 Windows Installer is not accessible when the computer is in Safe Mode. Exit Safe Mode and try again or try using System Restore to return your computer to a previous state. Available beginning with Windows Installer version 4.0. +ERROR_ROLLBACK_DISABLED 1653 Could not perform a multiple-package transaction because rollback has been disabled. Multiple-Package Installations cannot run if rollback is disabled. Available beginning with Windows Installer version 4.5. +ERROR_SUCCESS_REBOOT_REQUIRED 3010 A restart is required to complete the install. This message is indicative of a success. This does not include installs where the ForceReboot action is run. + + + diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/ConfigurationFile.ini b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/ConfigurationFile.ini new file mode 100644 index 0000000..6741609 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/ConfigurationFile.ini differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLEXPR_x64_ENU.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLEXPR_x64_ENU.exe new file mode 100644 index 0000000..a7697ee Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLEXPR_x64_ENU.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLSysClrTypes.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLSysClrTypes.msi new file mode 100644 index 0000000..592a3ad Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SQLSysClrTypes.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SharedManagementObjects.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SharedManagementObjects.msi new file mode 100644 index 0000000..280b500 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/SharedManagementObjects.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/msxml6.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/msxml6.msi new file mode 100644 index 0000000..feb1677 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/msxml6.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/sqlncli.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/sqlncli.msi new file mode 100644 index 0000000..4f983e5 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x64/ENU/sqlncli.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/ConfigurationFile.ini b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/ConfigurationFile.ini new file mode 100644 index 0000000..6741609 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/ConfigurationFile.ini differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLEXPR32_x86_ENU.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLEXPR32_x86_ENU.exe new file mode 100644 index 0000000..9943cbe Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLEXPR32_x86_ENU.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLSysClrTypes.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLSysClrTypes.msi new file mode 100644 index 0000000..f3832ff Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SQLSysClrTypes.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SharedManagementObjects.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SharedManagementObjects.msi new file mode 100644 index 0000000..0e4c0ea Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/SharedManagementObjects.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/msxml6.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/msxml6.msi new file mode 100644 index 0000000..16319fd Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/msxml6.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/sqlncli.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/sqlncli.msi new file mode 100644 index 0000000..1069ccd Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/3rdParty/x86/ENU/sqlncli.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/CalibryInstaller.msi b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/CalibryInstaller.msi new file mode 100644 index 0000000..46d429a Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/CalibryInstaller.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe new file mode 100644 index 0000000..d8546a9 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe.config b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe.config new file mode 100644 index 0000000..3f198b4 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/InstallationUI.exe.config @@ -0,0 +1,21 @@ + + + + +
+ + + + + + ..\3rdParty + + + CalibryInstaller.msi + + + {f357e98d-6ae9-4fe4-86be-a2254dec7f18} + + + + \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.dll b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.dll new file mode 100644 index 0000000..e560071 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.xml b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.xml new file mode 100644 index 0000000..170f92b --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/Microsoft.Deployment.WindowsInstaller.xml @@ -0,0 +1,7220 @@ + + + + Microsoft.Deployment.WindowsInstaller + + + + + Collection of column information related to a or + . + + + + + Creates a new ColumnCollection based on a specified list of columns. + + columns to be added to the new collection + + + + Creates a new ColumnCollection that is associated with a database table. + + view that contains the columns + + + + Not supported because the collection is read-only. + + information about the column being added + the collection is read-only + + + + Not supported because the collection is read-only. + + the collection is read-only + + + + Checks if a column with a given name exists in the collection. + + case-sensitive name of the column to look for + true if the column exists in the collection, false otherwise + + + + Checks if a column with a given name exists in the collection. + + column to look for, with case-sensitive name + true if the column exists in the collection, false otherwise + + + + Gets the index of a column within the collection. + + case-sensitive name of the column to look for + 0-based index of the column, or -1 if not found + + + + Copies the columns from this collection into an array. + + destination array to be filed + offset into the destination array where copying begins + + + + Not supported because the collection is read-only. + + column to remove + true if the column was removed, false if it was not found + the collection is read-only + + + + Gets an enumerator over the columns in the collection. + + An enumerator of ColumnInfo objects. + + + + Gets an enumerator over the columns in the collection. + + An enumerator of ColumnInfo objects. + + + + Creates ColumnInfo objects for the associated view. + + dynamically-generated list of columns + + + + Gets a list of column names or column-definition-strings for the + associated view. + + the view to that defines the columns + true to return types (column definition strings), + false to return names + list of column names or types + + + + Gets the number of columns in the collection. + + number of columns in the collection + + + + Gets a boolean value indicating whether the collection is read-only. + A ColumnCollection is read-only if it is associated with a + or a read-only . + + read-only status of the collection + + + + Gets information about a specific column in the collection. + + 1-based index into the column collection + is less + than 1 or greater than the number of columns in the collection + + + + Gets information about a specific column in the collection. + + case-sensitive name of a column collection + does + not exist in the collection + + + + Gets a string suitable for printing all the values of a record containing these columns. + + + + + Available values for the Attributes column of the Component table. + + + + + Local only - Component cannot be run from source. + +

+ Set this value for all components belonging to a feature to prevent the feature from being run-from-network or + run-from-source. Note that if a feature has no components, the feature always shows run-from-source and + run-from-my-computer as valid options. +

+
+ + + Component can only be run from source. + +

+ Set this bit for all components belonging to a feature to prevent the feature from being run-from-my-computer. + Note that if a feature has no components, the feature always shows run-from-source and run-from-my-computer + as valid options. +

+
+ + + Component can run locally or from source. + + + + + If this bit is set, the value in the KeyPath column is used as a key into the Registry table. + +

+ If the Value field of the corresponding record in the Registry table is null, the Name field in that record + must not contain "+", "-", or "*". For more information, see the description of the Name field in Registry + table. +

Setting this bit is recommended for registry entries written to the HKCU hive. This ensures the installer + writes the necessary HKCU registry entries when there are multiple users on the same machine.

+

+
+ + + If this bit is set, the installer increments the reference count in the shared DLL registry of the component's + key file. If this bit is not set, the installer increments the reference count only if the reference count + already exists. + + + + + If this bit is set, the installer does not remove the component during an uninstall. The installer registers + an extra system client for the component in the Windows Installer registry settings. + + + + + If this bit is set, the value in the KeyPath column is a key into the ODBCDataSource table. + + + + + If this bit is set, the installer reevaluates the value of the statement in the Condition column upon a reinstall. + If the value was previously False and has changed to true, the installer installs the component. If the value + was previously true and has changed to false, the installer removes the component even if the component has + other products as clients. + + + + + If this bit is set, the installer does not install or reinstall the component if a key path file or a key path + registry entry for the component already exists. The application does register itself as a client of the component. + +

+ Use this flag only for components that are being registered by the Registry table. Do not use this flag for + components registered by the AppId, Class, Extension, ProgId, MIME, and Verb tables. +

+
+ + + Set this bit to mark this as a 64-bit component. This attribute facilitates the installation of packages that + include both 32-bit and 64-bit components. If this bit is not set, the component is registered as a 32-bit component. + +

+ If this is a 64-bit component replacing a 32-bit component, set this bit and assign a new GUID in the + ComponentId column. +

+
+ + + Set this bit to disable registry reflection on all existing and new registry keys affected by this component. + +

+ If this bit is set, the Windows Installer calls the RegDisableReflectionKey on each key being accessed by the component. + This bit is available with Windows Installer version 4.0 and is ignored on 32-bit systems. +

+
+ + + [MSI 4.5] Set this bit for a component in a patch package to prevent leaving orphan components on the computer. + +

+ If a subsequent patch is installed, marked with the SupersedeEarlier flag in its MsiPatchSequence + table to supersede the first patch, Windows Installer 4.5 can unregister and uninstall components marked with the + UninstallOnSupersedence value. If the component is not marked with this bit, installation of a superseding patch can leave + behind an unused component on the computer. +

+
+ + + [MSI 4.5] If a component is marked with this attribute value in at least one package installed on the system, + the installer treats the component as marked in all packages. If a package that shares the marked component + is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system, + even if that highest version was installed by the package that is being uninstalled. + + + + + Defines flags for the Attributes column of the Control table. + + + + If this bit is set, the control is visible on the dialog box. + + + specifies if the given control is enabled or disabled. Most controls appear gray when disabled. + + + If this bit is set, the control is displayed with a sunken, three dimensional look. + + + The Indirect control attribute specifies whether the value displayed or changed by this control is referenced indirectly. + + + If this bit is set on a control, the associated property specified in the Property column of the Control table is an integer. + + + If this bit is set the text in the control is displayed in a right-to-left reading order. + + + If this style bit is set, text in the control is aligned to the right. + + + If this bit is set, the scroll bar is located on the left side of the control, otherwise it is on the right. + + + This is a combination of the RightToLeftReadingOrder, RightAligned, and LeftScroll attributes. + + + If this bit is set on a text control, the control is displayed transparently with the background showing through the control where there are no characters. + + + If this bit is set on a text control, the occurrence of the character "&" in a text string is displayed as itself. + + + If this bit is set the text in the control is displayed on a single line. + + + If this bit is set for a text control, the control will automatically attempt to format the displayed text as a number representing a count of bytes. + + + If this bit is set, fonts are created using the user's default UI code page. Otherwise it is created using the database code page. + + + If this bit is set on an Edit control, the installer creates a multiple line edit control with a vertical scroll bar. + + + This attribute creates an edit control for entering passwords. The control displays each character as an asterisk (*) as they are typed into the control. + + + If this bit is set on a ProgressBar control, the bar is drawn as a series of small rectangles in Microsoft Windows 95-style. Otherwise it is drawn as a single continuous rectangle. + + + If this bit is set, the control shows removable volumes. + + + If this bit is set, the control shows fixed internal hard drives. + + + If this bit is set, the control shows remote volumes. + + + If this bit is set, the control shows CD-ROM volumes. + + + If this bit is set, the control shows RAM disk volumes. + + + If this bit is set, the control shows floppy volumes. + + + Specifies whether or not the rollback backup files are included in the costs displayed by the VolumeCostList control. + + + If this bit is set, the items listed in the control are displayed in a specified order. Otherwise, items are displayed in alphabetical order. + + + If this bit is set on a combo box, the edit field is replaced by a static text field. This prevents a user from entering a new value and requires the user to choose only one of the predefined values. + + + If this bit is set on a check box or a radio button group, the button is drawn with the appearance of a push button, but its logic stays the same. + + + If this bit is set, the text in the control is replaced by a bitmap image. The Text column in the Control table is a foreign key into the Binary table. + + + If this bit is set, text is replaced by an icon image and the Text column in the Control table is a foreign key into the Binary table. + + + If this bit is set, the picture is cropped or centered in the control without changing its shape or size. + + + Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded. + + + Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded. + + + Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded. + + + If this bit is set, and the installation is not yet running with elevated privileges, the control is created with a UAC icon. + + + If this bit is set, the RadioButtonGroup has text and a border displayed around it. + + + + Defines flags for the Type column of the CustomAction table. + + + + Unspecified custom action type. + + + Target = entry point name + + + Target = command line args + + + Target = text string to be formatted and set into property + + + Target = entry point name, null if none to call + + + Target = entry point name, null if none to call + + + Target = property list for nested engine initialization + + + Source = File.File, file part of installation + + + Source = Directory.Directory, folder containing existing file + + + Source = Property.Property, full path to executable + + + Ignore action return status, continue running + + + Run asynchronously + + + Skip if UI sequence already run + + + Skip if UI sequence already run in same process + + + Run on client only if UI already run on client + + + Queue for execution within script + + + In conjunction with InScript: queue in Rollback script + + + In conjunction with InScript: run Commit ops from script on success + + + No impersonation, run in system context + + + Impersonate for per-machine installs on TS machines + + + Script requires 64bit process + + + Don't record the contents of the Target field in the log file + + + The custom action runs only when a patch is being uninstalled + + + + Defines flags for the Attributes column of the Dialog table. + + + + If this bit is set, the dialog is originally created as visible, otherwise it is hidden. + + + If this bit is set, the dialog box is modal, other dialogs of the same application cannot be put on top of it, and the dialog keeps the control while it is running. + + + If this bit is set, the dialog box can be minimized. This bit is ignored for modal dialog boxes, which cannot be minimized. + + + If this style bit is set, the dialog box will stop all other applications and no other applications can take the focus. + + + If this bit is set, the other dialogs stay alive when this dialog box is created. + + + If this bit is set, the dialog box periodically calls the installer. If the property changes, it notifies the controls on the dialog. + + + If this bit is set, the pictures on the dialog box are created with the custom palette (one per dialog received from the first control created). + + + If this style bit is set the text in the dialog box is displayed in right-to-left-reading order. + + + If this style bit is set, the text is aligned on the right side of the dialog box. + + + If this style bit is set, the scroll bar is located on the left side of the dialog box. + + + This is a combination of the RightToLeftReadingOrder, RightAligned, and the LeftScroll dialog style bits. + + + If this bit is set, the dialog box is an error dialog. + + + + Available values for the Attributes column of the Feature table. + + + + + Favor local - Components of this feature that are not marked for installation from source are installed locally. + +

+ A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource, + is installed locally. Components marked in the Component + table are always run from the source CD/server. The bits FavorLocal and FavorSource work with features not + listed by the ADVERTISE property. +

+
+ + + Components of this feature not marked for local installation are installed to run from the source + CD-ROM or server. + +

+ A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource, + is installed to run locally. Components marked (local-only) in the + Component table are always installed locally. The bits FavorLocal and FavorSource work with features + not listed by the ADVERTISE property. +

+
+ + + Set this attribute and the state of the feature is the same as the state of the feature's parent. + You cannot use this option if the feature is located at the root of a feature tree. + +

+ Omit this attribute and the feature state is determined according to DisallowAdvertise and + FavorLocal and FavorSource. +

To guarantee that the child feature's state always follows the state of its parent, even when the + child and parent are initially set to absent in the SelectionTree control, you must include both + FollowParent and UIDisallowAbsent in the attributes of the child feature.

+

Note that if you set FollowParent without setting UIDisallowAbsent, the installer cannot force + the child feature out of the absent state. In this case, the child feature matches the parent's + installation state only if the child is set to something other than absent.

+

Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.

+

+
+ + + Set this attribute and the feature state is Advertise. + +

+ If the feature is listed by the ADDDEFAULT property this bit is ignored and the feature state is determined + according to FavorLocal and FavorSource. +

Omit this attribute and the feature state is determined according to DisallowAdvertise and FavorLocal + and FavorSource.

+

+
+ + + Set this attribute to prevent the feature from being advertised. + +

+ Note that this bit works only with features that are listed by the ADVERTISE property. +

Set this attribute and if the listed feature is not a parent or child, the feature is installed according to + FavorLocal and FavorSource.

+

Set this attribute for the parent of a listed feature and the parent is installed.

+

Set this attribute for the child of a listed feature and the state of the child is Absent.

+

Omit this attribute and if the listed feature is not a parent or child, the feature state is Advertise.

+

Omit this attribute and if the listed feature is a parent or child, the state of both features is Advertise.

+

+
+ + + Set this attribute and the user interface does not display an option to change the feature state + to Absent. Setting this attribute forces the feature to the installation state, whether or not the + feature is visible in the UI. + +

+ Omit this attribute and the user interface displays an option to change the feature state to Absent. +

Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.

+

Setting this attribute not only affects the UI, but also forces the feature to the install state whether + the feature is visible in the UI or not.

+

+
+ + + Set this attribute and advertising is disabled for the feature if the operating system shell does not + support Windows Installer descriptors. + + + + + Available values for the Attributes column of the File table. + + + + No attributes. + + + Read-only. + + + Hidden. + + + System. + + + The file is vital for the proper operation of the component to which it belongs. + + + The file contains a valid checksum. A checksum is required to repair a file that has become corrupted. + + + This bit must only be added by a patch and if the file is being added by the patch. + + + + The file's source type is uncompressed. If set, ignore the WordCount summary information property. If neither + Noncompressed nor Compressed are set, the compression state of the file is specified by the WordCount summary + information property. Do not set both Noncompressed and Compressed. + + + + + The file's source type is compressed. If set, ignore the WordCount summary information property. If neither + Noncompressed or Compressed are set, the compression state of the file is specified by the WordCount summary + information property. Do not set both Noncompressed and Compressed. + + + + + Defines values for the Action column of the IniFile and RemoveIniFile tables. + + + + Creates or updates a .ini entry. + + + Creates a .ini entry only if the entry does not already exist. + + + Deletes .ini entry. + + + Creates a new entry or appends a new comma-separated value to an existing entry. + + + Deletes a tag from a .ini entry. + + + + Defines values for the Type column of the CompLocator, IniLocator, and RegLocator tables. + + + + Key path is a directory. + + + Key path is a file name. + + + Key path is a registry value. + + + Set this bit to have the installer search the 64-bit portion of the registry. + + + + Defines values for the Root column of the Registry, RemoveRegistry, and RegLocator tables. + + + + HKEY_CURRENT_USER for a per-user installation, + or HKEY_LOCAL_MACHINE for a per-machine installation. + + + HKEY_CLASSES_ROOT + + + HKEY_CURRENT_USER + + + HKEY_LOCAL_MACHINE + + + HKEY_USERS + + + + Defines values for the InstallMode column of the RemoveFile table. + + + + Never remove. + + + Remove when the associated component is being installed (install state = local or source). + + + Remove when the associated component is being removed (install state = absent). + + + + Defines values for the ServiceType, StartType, and ErrorControl columns of the ServiceInstall table. + + + + No flags. + + + A Win32 service that runs its own process. + + + A Win32 service that shares a process. + + + A Win32 service that interacts with the desktop. + This value cannot be used alone and must be added to either + or . + + + Service starts during startup of the system. + + + Service starts when the service control manager calls the StartService function. + + + Specifies a service that can no longer be started. + + + Logs the error, displays a message box and continues the startup operation. + + + Logs the error if it is possible and the system is restarted with the last configuration + known to be good. If the last-known-good configuration is being started, the startup operation fails. + + + When combined with other error flags, specifies that the overall install should fail if + the service cannot be installed into the system. + + + + Defines values for the Event column of the ServiceControl table. + + + + No control events. + + + During an install, starts the service during the StartServices action. + + + During an install, stops the service during the StopServices action. + + + During an install, deletes the service during the DeleteServices action. + + + During an uninstall, starts the service during the StartServices action. + + + During an uninstall, stops the service during the StopServices action. + + + During an uninstall, deletes the service during the DeleteServices action. + + + + Defines values for the StyleBits column of the TextStyle table. + + + + Bold + + + Italic + + + Underline + + + Strike out + + + + Defines values for the Attributes column of the Upgrade table. + + + + Migrates feature states by enabling the logic in the MigrateFeatureStates action. + + + Detects products and applications but does not remove. + + + Continues installation upon failure to remove a product or application. + + + Detects the range of versions including the value in VersionMin. + + + Dectects the range of versions including the value in VersionMax. + + + Detects all languages, excluding the languages listed in the Language column. + + + + Defines a single column of a table in an installer database. + + Once created, a ColumnInfo object is immutable. + + + + Creates a new ColumnInfo object from a column definition. + + name of the column + column definition string + + + + + Creates a new ColumnInfo object from a list of parameters. + + name of the column + type of the column; must be one of the following: + Int16, Int32, String, or Stream + the maximum number of characters for String columns; + ignored for other column types + true if the column is required to have a non-null value + + + + Creates a new ColumnInfo object from a list of parameters. + + name of the column + type of the column; must be one of the following: + Int16, Int32, String, or Stream + the maximum number of characters for String columns; + ignored for other column types + true if the column is required to have a non-null value + true to if the column is only in-memory and + not persisted with the database + for String columns, indicates the column + is localizable; ignored for other column types + + + + Gets the name of the column. + + Name of the column. + + + + Gets the name of the column. + + name of the column + + + + Gets the type of the column as a System.Type. This is one of the following: Int16, Int32, String, or Stream + + type of the column + + + + Gets the type of the column as a System.Data.DbType. This is one of the following: Int16, Int32, String, or Binary + + equivalent DbType of the column + + + + Gets the size of the column. + + The size of integer columns this is either 2 or 4. For string columns this is the maximum + recommended length of the string, or 0 for unlimited length. For stream columns, 0 is returned. + + + + Gets a value indicating whether the column must be non-null when inserting a record. + + required status of the column + + + + Gets a value indicating whether the column is temporary. Temporary columns are not persisted + when the database is saved to disk. + + temporary status of the column + + + + Gets a value indicating whether the column is a string column that is localizable. + + localizable status of the column + + + + Gets an SQL fragment that can be used to create this column within a CREATE TABLE statement. + + SQL fragment to be used for creating the column +

+ Examples: + + LONG + SHORT TEMPORARY + CHAR(0) LOCALIZABLE + CHAR(72) NOT NULL LOCALIZABLE + OBJECT + +

+
+ + + Gets a short string defining the type and size of the column. + + + The definition string consists + of a single letter representing the data type followed by the width of the column (in characters + when applicable, bytes otherwise). A width of zero designates an unbounded width (for example, + long text fields and streams). An uppercase letter indicates that null values are allowed in + the column. + +

+ + s? - String, variable length (?=1-255) + s0 - String, variable length + i2 - Short integer + i4 - Long integer + v0 - Binary Stream + g? - Temporary string (?=0-255) + j? - Temporary integer (?=0,1,2,4) + l? - Localizable string, variable length (?=1-255) + l0 - Localizable string, variable length + +

+
+ + + Accessor for information about components within the context of an installation session. + + + + + Checks if the collection contains a component. + + name of the component + true if the component is in the collection, else false + + + + Copies the features into an array. + + array that receives the features + offset into the array + + + + Enumerates the components in the collection. + + an enumerator over all features in the collection + + + + Gets information about a component within the context of an installation session. + + name of the component + component object + + + + Gets the number of components defined for the product. + + + + + Provides access to information about a component within the context of an installation session. + + + + + Gets disk space per drive required to install a component. + + Requested component state + A list of InstallCost structures, specifying the cost for each drive for the component +

+ Win32 MSI API: + MsiEnumComponentCosts +

+
+ + + Gets the name of the component (primary key in the Component table). + + + + + Gets the current install state of the designated Component. + + the Session handle is invalid + an unknown Component was requested +

+ Win32 MSI API: + MsiGetComponentState +

+
+ + + Gets or sets the action state of the designated Component. + + the Session handle is invalid + an unknown Component was requested + the user exited the installation +

+ Win32 MSI APIs: + MsiGetComponentState, + MsiSetComponentState +

+
+ + + Represents an instance of a registered component. + + + + + Subclasses of this abstract class represent an instance + of a registered feature or component. + + + + + Gets the product that this item is a part of. + + + + + Gets the current installation state of the item. + + + + + Creates a new ComponentInstallation, automatically detecting the + product that the component is a part of. + + component GUID +

+ Win32 MSI API: + MsiGetProductCode +

+
+ + + Creates a new ComponentInstallation for a component installed by a + specific product. + + component GUID + ProductCode GUID + + + + Gets the set of installed components for all products. + + The installer configuration data is corrupt +

+ Win32 MSI API: + MsiEnumComponents +

+
+ + + Gets the component code (GUID) of the component. + + + + + Gets all client products of a specified component. + + enumeration over all client products of the component + The installer configuration data is corrupt +

+ Because clients are not ordered, any new component has an arbitrary index. + This means that the property may return clients in any order. +

+ Win32 MSI API: + MsiEnumClients +

+
+ + + Gets the installed state of a component. + + the installed state of the component, or InstallState.Unknown + if this component is not part of a product +

+ Win32 MSI API: + MsiGetComponentPath +

+
+ + + Gets the full path to an installed component. If the key path for the component is a + registry key then the registry key is returned. + + The file or registry keypath to the component, or null if the component is not available. + An unknown product or component was specified + The installer configuration data is corrupt +

+ If the component is a registry key, the registry roots are represented numerically. + For example, a registry path of "HKEY_CURRENT_USER\SOFTWARE\Microsoft" would be returned + as "01:\SOFTWARE\Microsoft". The registry roots returned are defined as follows: + HKEY_CLASSES_ROOT=00, HKEY_CURRENT_USER=01, HKEY_LOCAL_MACHINE=02, HKEY_USERS=03, + HKEY_PERFORMANCE_DATA=04 +

+ Win32 MSI APIs: + MsiGetComponentPath, + MsiLocateComponent +

+
+ + + Gets the set of registered qualifiers for the component. + + Enumeration of the qulifiers for the component. + The installer configuration data is corrupt +

+ Because qualifiers are not ordered, any new qualifier has an arbitrary index, + meaning the function can return qualifiers in any order. +

+ Win32 MSI API: + MsiEnumComponentQualifiers +

+
+ + + Holds data about a component qualifier. + +

+ Win32 MSI API: + MsiEnumComponentQualifiers +

+
+ + + Gets the qualifier code. + + + + + Gets the qualifier data. + + + + + Marks a method as a custom action entry point. + +

+ A custom action method must be defined as public and static, + take a single object as a parameter, + and return an enumeration value. +

+
+ + + Name of the custom action entrypoint, or null if the same as the method name. + + + + + Marks a method as a custom action entry point. + + + + + Marks a method as a custom action entry point. + + Name of the function to be exported, + defaults to the name of this method + + + + Gets or sets the name of the custom action entrypoint. A null + value defaults to the name of the method. + + name of the custom action entrypoint, or null if none was specified + + + + Contains a collection of key-value pairs suitable for passing between + immediate and deferred/rollback/commit custom actions. + + + Call the method to get a string + suitable for storing in a property and reconstructing the custom action data later. + + + + + + + "CustomActionData" literal property name. + + + + + Creates a new empty custom action data object. + + + + + Reconstructs a custom action data object from data that was previously + persisted in a string. + + Previous output from . + + + + Adds a key and value to the data collection. + + Case-sensitive data key. + Data value (may be null). + the key does not consist solely of letters, + numbers, and the period, underscore, and space characters. + + + + Adds a value to the data collection, using XML serialization to persist the object as a string. + + Case-sensitive data key. + Data value (may be null). + the key does not consist solely of letters, + numbers, and the period, underscore, and space characters. + The value type does not support XML serialization. + The value could not be serialized. + + + + Gets a value from the data collection, using XML serialization to load the object from a string. + + Case-sensitive data key. + The value could not be deserialized. + + + + Determines whether the data contains an item with the specified key. + + Case-sensitive data key. + true if the data contains an item with the key; otherwise, false + + + + Removes the item with the specified key from the data. + + Case-sensitive data key. + true if the item was successfully removed from the data; + false if an item with the specified key was not found + + + + Gets the value with the specified key. + + Case-sensitive data key. + Value associated with the specified key, or + null if an item with the specified key was not found + true if the data contains an item with the specified key; otherwise, false. + + + + Adds an item with key and value to the data collection. + + Case-sensitive data key, with a data value that may be null. + the key does not consist solely of letters, + numbers, and the period, underscore, and space characters. + + + + Removes all items from the data. + + + + + Determines whether the data contains a specified item. + + The data item to locate. + true if the data contains the item; otherwise, false + + + + Copies the data to an array, starting at a particular array index. + + Destination array. + Index in the array at which copying begins. + + + + Removes an item from the data. + + The item to remove. + true if the item was successfully removed from the data; + false if the item was not found + + + + Returns an enumerator that iterates through the collection. + + An enumerator that can be used to iterate through the collection. + + + + Returns an enumerator that iterates through the collection. + + An enumerator that can be used to iterate through the collection. + + + + Gets a string representation of the data suitable for persisting in a property. + + Data string in the form "Key1=Value1;Key2=Value2" + + + + Ensures that a key contains valid characters. + + key to be validated + the key does not consist solely of letters, + numbers, and the period, underscore, and space characters. + + + + Serializes a value into an XML string. + + Type of the value. + Value to be serialized. + Serialized value data as a string. + + + + Deserializes a value from an XML string. + + Expected type of the value. + Serialized value data. + Deserialized value object. + + + + Escapes a value string by doubling any data-separator (semicolon) characters. + + + Escaped value string + + + + Unescapes a value string by undoubling any doubled data-separator (semicolon) characters. + + + Unescaped value string + + + + Loads key-value pairs from a string into the data collection. + + key-value pair list of the form returned by + + + + Gets a collection object containing all the keys of the data. + + + + + Gets a collection containing all the values of the data. + + + + + Gets or sets a data value with a specified key. + + Case-sensitive data key. + the key does not consist solely of letters, + numbers, and the period, underscore, and space characters. + + + + Gets the number of items in the data. + + + + + Gets a value indicating whether the data is read-only. + + + + + Managed-code portion of the custom action proxy. + + + + + Invokes a managed custom action method. + + Integer handle to the installer session. + Name of the custom action entrypoint. This must + either map to an entrypoint definition in the customActions + config section, or be an explicit entrypoint of the form: + "AssemblyName!Namespace.Class.Method" + Pointer to a delegate used to + make remote API calls, if this custom action is running out-of-proc. + The value returned by the custom action method, + or ERROR_INSTALL_FAILURE if the custom action could not be invoked. + + + + Checks the "MMsiBreak" environment variable for any matching custom action names. + + List of names to search for in the environment + variable string. + True if a match was found, else false. + + + + Locates and parses an entrypoint mapping in CustomAction.config. + + Installer session handle, just used for logging. + Custom action entrypoint name: the key value + in an item in the customActions section of the config file. + Returned display name of the assembly from + the entrypoint mapping. + Returned class name of the entrypoint mapping. + Returned method name of the entrypoint mapping. + True if the entrypoint was found, false if not or if some error + occurred. + + + + Uses reflection to load the assembly and class and find the method. + + Installer session handle, just used for logging. + Display name of the assembly containing the + custom action method. + Fully-qualified name of the class containing the + custom action method. + Name of the custom action method. + The method, or null if not found. + + + + Checks if a method has the right return and paramater types + for a custom action, and that it is marked by a CustomActionAttribute. + + Method to be checked. + True if the method is a valid custom action, else false. + + + + Accesses a Windows Installer database. + +

+ The method must be called before the Database is closed to write out all + persistent changes. If the Commit method is not called, the installer performs an implicit + rollback upon object destruction. +

+ The client can use the following procedure for data access: + Obtain a Database object using one of the Database constructors. + Initiate a query using a SQL string by calling the + method of the Database. + Set query parameters in a and execute the database + query by calling the method of the . This + produces a result that can be fetched or updated. + Call the method of the View repeatedly to return + Records. + Update database rows of a Record object obtained by the Fetch method using + one of the methods of the View. + Release the query and any unfetched records by calling the + method of the View. + Persist any database updates by calling the Commit method of the Database. + + +

+
+ + + Base class for Windows Installer handle types (Database, View, Record, SummaryInfo). + +

+ These classes implement the interface, because they + hold unmanaged resources (MSI handles) that should be properly disposed + when no longer needed. +

+
+ + + Constructs a handle object from a native integer handle. + + Native integer handle. + true to close the handle when this object is disposed or finalized + + + + Closes the handle. After closing a handle, further method calls may throw an . + +

+ The finalizer of this class will NOT close the handle if it is still open, + because finalization can run on a separate thread from the application, + resulting in potential problems if handles are closed from that thread. + It is best that the handle be closed manually as soon as it is no longer needed, + as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiCloseHandle +

+ +
+ + + Closes the handle. After closing a handle, further method calls may throw an . + +

+ The finalizer of this class will NOT close the handle if it is still open, + because finalization can run on a separate thread from the application, + resulting in potential problems if handles are closed from that thread. + It is best that the handle be closed manually as soon as it is no longer needed, + as leaving lots of unused handles open can degrade performance. +

+ This method is merely an alias for the method. +

+ Win32 MSI API: + MsiCloseHandle +

+
+ + + Tests whether this handle object is equal to another handle object. Two handle objects are equal + if their types are the same and their native integer handles are the same. + + The handle object to compare with the current handle object. + true if the specified handle object is equal to the current handle object; otherwise false + + + + Gets a hash value for the handle object. + + A hash code for the handle object. +

+ The hash code is derived from the native integer handle. +

+
+ + + Closes the handle. After closing a handle, further method calls may throw an . + + If true, the method has been called directly or indirectly by a user's code, + so managed and unmanaged resources will be disposed. If false, the method has been called by the + runtime from inside the finalizer, and only unmanaged resources will be disposed. + + + + Gets the native integer handle. + + + + + Checks if the handle is closed. When closed, method calls on the handle object may throw an . + + + + + Gets an object that can be used internally for safe syncronization. + + + + + Opens an existing database in read-only mode. + + Path to the database file. + the database could not be created/opened +

+ Because this constructor initiates database access, it cannot be used with a + running installation. +

+ The Database object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiOpenDatabase +

+
+ + + Opens an existing database with another database as output. + + Path to the database to be read. + Open mode for the database + Database object representing the created or opened database + the database could not be created/opened +

+ When a database is opened as the output of another database, the summary information stream + of the output database is actually a read-only mirror of the original database and thus cannot + be changed. Additionally, it is not persisted with the database. To create or modify the + summary information for the output database it must be closed and re-opened. +

+ The Database object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ The database is opened in mode, and will be + automatically commited when it is closed. +

+ Win32 MSI API: + MsiOpenDatabase +

+
+ + + Opens an existing database or creates a new one. + + Path to the database file. If an empty string + is supplied, a temporary database is created that is not persisted. + Open mode for the database + the database could not be created/opened +

+ Because this constructor initiates database access, it cannot be used with a + running installation. +

+ The database object should be d after use. + The finalizer will close the handle if it is still open, however due to the nondeterministic + nature of finalization it is best that the handle be closed manually as soon as it is no + longer needed, as leaving lots of unused handles open can degrade performance. +

+ A database opened in or + mode will be automatically commited when it is + closed. However a database opened in or + mode must have the method + called before it is closed, otherwise no changes will be persisted. +

+ Win32 MSI API: + MsiOpenDatabase +

+
+ + + Creates a new database from an MSI handle. + + Native MSI database handle. + True if the handle should be closed + when the database object is disposed + Path of the database file, if known + Mode the handle was originally opened in + + + + Creates a new Database object from an integer database handle. + +

+ This method is only provided for interop purposes. A Database object + should normally be obtained from or + a public Database constructor. +

+ Integer database handle + true to close the handle when this object is disposed +
+ + + Schedules a file or directory for deletion after the database handle is closed. + + File or directory path to be deleted. All files and subdirectories + under a directory are deleted. +

+ Once an item is scheduled, it cannot be unscheduled. +

+ The items cannot be deleted if the Database object is auto-disposed by the + garbage collector; the handle must be explicitly closed. +

+ Files which are read-only or otherwise locked cannot be deleted, + but they will not cause an exception to be thrown. +

+
+ + + Merges another database with this database. + + The database to be merged into this database + Optional name of table to contain the names of the tables containing + merge conflicts, the number of conflicting rows within the table, and a reference to the table + with the merge conflict. + merge failed due to a schema difference or data conflict + the Database handle is invalid +

+ Merge does not copy over embedded cabinet files or embedded transforms from the + reference database into the target database. Embedded data streams that are listed in the + Binary table or Icon table are copied from the reference database to the target database. + Storage embedded in the reference database are not copied to the target database. +

+ The Merge method merges the data of two databases. These databases must have the same + codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists + if the data in any row in the first database differs from the data in the corresponding row + of the second database. Corresponding rows are in the same table of both databases and have + the same primary key in both databases. The tables of non-conflicting databases must have + the same number of primary keys, same number of columns, same column types, same column names, + and the same data in rows with identical primary keys. Temporary columns however don't matter + in the column count and corresponding tables can have a different number of temporary columns + without creating conflict as long as the persistent columns match. +

+ If the number, type, or name of columns in corresponding tables are different, the + schema of the two databases are incompatible and the installer will stop processing tables + and the merge fails. The installer checks that the two databases have the same schema before + checking for row merge conflicts. If the schemas are incompatible, the databases have be + modified. +

+ If the data in particular rows differ, this is a row merge conflict, the merge fails + and creates a new table with the specified name. The first column of this table is the name + of the table having the conflict. The second column gives the number of rows in the table + having the conflict. +

+ Win32 MSI API: + MsiDatabaseMerge +

+
+ + + Merges another database with this database. + + The database to be merged into this database + merge failed due to a schema difference or data conflict + the Database handle is invalid +

+ MsiDatabaseMerge does not copy over embedded cabinet files or embedded transforms from + the reference database into the target database. Embedded data streams that are listed in + the Binary table or Icon table are copied from the reference database to the target database. + Storage embedded in the reference database are not copied to the target database. +

+ The Merge method merges the data of two databases. These databases must have the same + codepage. The merge fails if any tables or rows in the databases conflict. A conflict exists + if the data in any row in the first database differs from the data in the corresponding row + of the second database. Corresponding rows are in the same table of both databases and have + the same primary key in both databases. The tables of non-conflicting databases must have + the same number of primary keys, same number of columns, same column types, same column names, + and the same data in rows with identical primary keys. Temporary columns however don't matter + in the column count and corresponding tables can have a different number of temporary columns + without creating conflict as long as the persistent columns match. +

+ If the number, type, or name of columns in corresponding tables are different, the + schema of the two databases are incompatible and the installer will stop processing tables + and the merge fails. The installer checks that the two databases have the same schema before + checking for row merge conflicts. If the schemas are incompatible, the databases have be + modified. +

+ Win32 MSI API: + MsiDatabaseMerge +

+
+ + + Checks whether a table exists and is persistent in the database. + + The table to the checked + true if the table exists and is persistent in the database; false otherwise + the table is unknown + the Database handle is invalid +

+ To check whether a table exists regardless of persistence, + use . +

+ Win32 MSI API: + MsiDatabaseIsTablePersistent +

+
+ + + Checks whether a table contains a persistent column with a given name. + + The table to the checked + The name of the column to be checked + true if the column exists in the table; false if the column is temporary or does not exist. + the View could not be executed + the Database handle is invalid +

+ To check whether a column exists regardless of persistence, + use . +

+
+ + + Gets the count of all rows in the table. + + Name of the table whose rows are to be counted + The count of all rows in the table + the View could not be executed + the Database handle is invalid + + + + Gets the count of all rows in the table that satisfy a given condition. + + Name of the table whose rows are to be counted + Conditional expression, such as could be placed on the end of a SQL WHERE clause + The count of all rows in the table satisfying the condition + the SQL WHERE syntax is invalid + the View could not be executed + the Database handle is invalid + + + + Finalizes the persistent form of the database. All persistent data is written + to the writeable database, and no temporary columns or rows are written. + + the Database handle is invalid +

+ For a database open in mode, this method has no effect. +

+ For a database open in or + mode, it is not necessary to call this method because the database will be automatically committed + when it is closed. However this method may be called at any time to persist the current state of tables + loaded into memory. +

+ For a database open in or + mode, no changes will be persisted until this method is called. If the database object is closed without + calling this method, the database file remains unmodified. +

+ Win32 MSI API: + MsiDatabaseCommit +

+
+ + + Copies the structure and data from a specified table to a text archive file. + + Name of the table to be exported + Path to the file to be created + the file path is invalid + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseExport +

+
+ + + Imports a database table from a text archive file, dropping any existing table. + + Path to the file to be imported. + The table name is specified within the file. + the file path is invalid + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseImport +

+
+ + + Exports all database tables, streams, and summary information to archive files. + + Path to the directory where archive files will be created + the directory path is invalid + the Database handle is invalid +

+ The directory will be created if it does not already exist. +

+ Win32 MSI API: + MsiDatabaseExport +

+
+ + + Imports all database tables, streams, and summary information from archive files. + + Path to the directory from which archive files will be imported + the directory path is invalid + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseImport +

+
+ + + Creates a new record object with the requested number of fields. + + Required number of fields, which may be 0. + The maximum number of fields in a record is limited to 65535. + A new record object that can be used with the database. +

+ This method is equivalent to directly calling the + constructor in all cases outside of a custom action context. When in a + custom action session, this method allows creation of a record that can + work with a database other than the session database. +

+ The Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiCreateRecord +

+
+ + + Returns the file path of this database, or the handle value if a file path was not specified. + + + + + Closes the database handle. After closing a handle, further method calls may throw . + + If true, the method has been called directly or + indirectly by a user's code, so managed and unmanaged resources will be + disposed. If false, only unmanaged resources will be disposed. + + + + Gets a View object representing the query specified by a SQL string. + + SQL query string, which may contain format items + Zero or more objects to format + A View object representing the query specified by a SQL string + the SQL syntax is invalid + the Database handle is invalid +

+ The parameter is formatted using . +

+ The View object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiDatabaseOpenView +

+
+ + + Executes the query specified by a SQL string. The query may not be a SELECT statement. + + SQL query string, which may contain format items + Zero or more objects to format + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ The parameter is formatted using + . +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute +

+
+ + + Executes the query specified by a SQL string. The query may not be a SELECT statement. + + SQL query string + Optional Record object containing the values that replace + the parameter tokens (?) in the SQL query. + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute +

+
+ + + Executes the specified SQL SELECT query and returns all results. + + SQL query string, which may contain format items + Zero or more objects to format + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ The parameter is formatted using + . +

+ Multiple rows columns will be collapsed into a single one-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns all results. + + SQL SELECT query string + Optional Record object containing the values that replace + the parameter tokens (?) in the SQL query. + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ Multiple rows columns will be collapsed into a single one-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns all results as integers. + + SQL query string, which may contain format items + Zero or more objects to format + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ The parameter is formatted using + . +

+ Multiple rows columns will be collapsed into a single one-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns all results as integers. + + SQL SELECT query string + Optional Record object containing the values that replace + the parameter tokens (?) in the SQL query. + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ Multiple rows columns will be collapsed into a single one-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns all results as strings. + + SQL query string, which may contain format items + Zero or more objects to format + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ The parameter is formatted using + . +

+ Multiple rows columns will be collapsed into a single on-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns all results as strings. + + SQL SELECT query string + Optional Record object containing the values that replace + the parameter tokens (?) in the SQL query. + All results combined into an array + the SQL syntax is invalid + the View could not be executed + the Database handle is invalid +

+ Multiple rows columns will be collapsed into a single on-dimensional list. +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns a single result. + + SQL query string, which may contain format items + Zero or more objects to format + First field of the first result + the SQL syntax is invalid + the View could not be executed + or the query returned 0 results + the Database handle is invalid +

+ The parameter is formatted using + . +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Executes the specified SQL SELECT query and returns a single result. + + SQL SELECT query string + Optional Record object containing the values that replace + the parameter tokens (?) in the SQL query. + First field of the first result + the SQL syntax is invalid + the View could not be executed + or the query returned 0 results + the Database handle is invalid +

+ Win32 MSI APIs: + MsiDatabaseOpenView, + MsiViewExecute, + MsiViewFetch +

+
+ + + Creates a transform that, when applied to the object database, results in the reference database. + + Database that does not include the changes + Name of the generated transform file, or null to only + check whether or not the two database are identical + true if a transform is generated, or false if a transform is not generated + because there are no differences between the two databases. + the transform could not be generated + a Database handle is invalid +

+ A transform can add non-primary key columns to the end of a table. A transform cannot + be created that adds primary key columns to a table. A transform cannot be created that + changes the order, names, or definitions of columns. +

+ If the transform is to be applied during an installation you must use the + method to populate the + summary information stream. +

+ Win32 MSI API: + MsiDatabaseGenerateTransform +

+
+ + + Creates and populates the summary information stream of an existing transform file, and + fills in the properties with the base and reference ProductCode and ProductVersion. + + Database that does not include the changes + Name of the generated transform file + Error conditions that should be suppressed + when the transform is applied + Defines which properties should be validated + to verify that this transform can be applied to a database. + the transform summary info could not be + generated + a Database handle is invalid +

+ Win32 MSI API: + MsiCreateTransformSummaryInfo +

+
+ + + Apply a transform to the database, recording the changes in the "_TransformView" table. + + Path to the transform file + the transform could not be applied + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseApplyTransform +

+
+ + + Apply a transform to the database, suppressing any error conditions + specified by the transform's summary information. + + Path to the transform file + the transform could not be applied + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseApplyTransform +

+
+ + + Apply a transform to the database, specifying error conditions to suppress. + + Path to the transform file + Error conditions that are to be suppressed + the transform could not be applied + the Database handle is invalid +

+ Win32 MSI API: + MsiDatabaseApplyTransform +

+
+ + + Checks whether a transform is valid for this Database, according to its validation data and flags. + + Path to the transform file + true if the transform can be validly applied to this Database; false otherwise + the transform could not be applied + the Database handle is invalid + + + + Checks whether a transform is valid for this Database, according to its SummaryInfo data. + + SummaryInfo data of a transform file + true if the transform can be validly applied to this Database; false otherwise + error processing summary info + the Database or SummaryInfo handle is invalid + + + + Gets the file path the Database was originally opened from, or null if not known. + + + + + Gets the open mode for the database. + + + + + Gets a boolean value indicating whether this database was opened in read-only mode. + +

+ Win32 MSI API: + MsiGetDatabaseState +

+
+ + + Gets the collection of tables in the Database. + + + + + Gets or sets the code page of the Database. + + error exporting/importing the codepage data + the Database handle is invalid +

+ Getting or setting the code page is a slow operation because it involves an export or import + of the codepage data to/from a temporary file. +

+
+ + + Gets the SummaryInfo object for this database that can be used to examine and modify properties + to the summary information stream. + + the Database handle is invalid +

+ The object returned from this property does not need to be explicitly persisted or closed. + Any modifications will be automatically saved when the database is committed. +

+ Win32 MSI API: + MsiGetSummaryInformation +

+
+ + + Managed-code portion of the embedded UI proxy. + + + + + Initializes managed embedded UI by loading the UI class and invoking its Initialize method. + + Integer handle to the installer session. + Name of the class that implements the embedded UI. This must + be of the form: "AssemblyName!Namespace.Class" + On entry, contains the current UI level for the installation. After this + method returns, the installer resets the UI level to the returned value of this parameter. + 0 if the embedded UI was successfully loaded and initialized, + ERROR_INSTALL_USEREXIT if the user canceled the installation during initialization, + or ERROR_INSTALL_FAILURE if the embedded UI could not be initialized. + + Due to interop limitations, the successful resulting UILevel is actually returned + as the high-word of the return value instead of via a ref parameter. + + + + + Passes a progress message to the UI class. + + Installer message type and message box options. + Handle to a record containing message data. + Return value returned by the UI class. + + + + Passes a shutdown message to the UI class. + + + + + Instantiates a UI class from a given assembly and class name. + + Installer session, for logging. + Name of the class that implements the embedded UI. This must + be of the form: "AssemblyName!Namespace.Class" + Interface on the UI class for handling UI messages. + + + + Specifies a return status value for custom actions. + + + + Action completed successfully. + + + Skip remaining actions, not an error. + + + User terminated prematurely. + + + Unrecoverable error or unhandled exception occurred. + + + Action not executed. + + + + Specifies the open mode for a . + + + + Open a database read-only, no persistent changes. + + + Open a database read/write in transaction mode. + + + Open a database direct read/write without transaction. + + + Create a new database, transact mode read/write. + + + Create a new database, direct mode read/write. + + + + Log modes available for + and . + + + + Disable logging. + + + Log out of memory or fatal exit information. + + + Log error messages. + + + Log warning messages. + + + Log user requests. + + + Log status messages that are not displayed. + + + Log request to determine a valid source location. + + + Log insufficient disk space error. + + + Log the start of installation actions. + + + Log the data record for installation actions. + + + Log parameters for user-interface initialization. + + + Log the property values at termination. + + + + Sends large amounts of information to log file not generally useful to users. + May be used for support. + + + + + Log extra debugging information. + + + + + Log only on error. + + + + + Log progress bar information. This message includes information on units so far and total number + of units. See for an explanation of the message format. This message + is only sent to an external user interface and is not logged. + + + + + If this is not a quiet installation, then the basic UI has been initialized. If this is a full + UI installation, the Full UI is not yet initialized. This message is only sent to an external + user interface and is not logged. + + + + + If a full UI is being used, the full UI has ended. If this is not a quiet installation, the basic + UI has not yet ended. This message is only sent to an external user interface and is not logged. + + + + + Sent prior to display of the Full UI dialog. This message is only sent to an external user + interface and is not logged. + + + + + List of files in use that need to be replaced. + + + + + [MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart. + + + + + Type of message to be processed by , + , or . + + + + Premature termination, possibly fatal OOM. + + + Formatted error message. + + + Formatted warning message. + + + User request message. + + + Informative message for log. + + + List of files in use that need to be replaced. + + + Request to determine a valid source location. + + + Insufficient disk space message. + + + Start of action: action name & description. + + + Formatted data associated with individual action item. + + + Progress gauge info: units so far, total. + + + Product info for dialog: language Id, dialog caption. + + + Sent prior to UI initialization, no string data. + + + Sent after UI termination, no string data. + + + Sent prior to display or authored dialog or wizard. + + + [MSI 4.0] List of apps that the user can request Restart Manager to shut down and restart. + + + [MSI 4.5] Sent prior to install of a product. + + + [MSI 4.5] Sent after install of a product. + + + + Specifies the install mode for or . + + + + Provide the component only if the feature's installation state is . + + + Only check that the component is registered, without verifying that the key file of the component exists. + + + Provide the component only if the feature exists. + + + Provide the component and perform any installation necessary to provide the component. + + + + Specifies the run mode for . + + + + The administrative mode is installing, or the product is installing. + + + The advertisements are installing or the product is installing or updating. + + + An existing installation is being modified or there is a new installation. + + + Rollback is enabled. + + + The log file is active. It was enabled prior to the installation session. + + + Execute operations are spooling or they are in the determination phase. + + + A reboot is necessary after a successful installation (settable). + + + A reboot is necessary to continue the installation (settable). + + + Files from cabinets and Media table files are installing. + + + The source LongFileNames is suppressed through the PID_MSISOURCE summary property. + + + The target LongFileNames is suppressed through the SHORTFILENAMES property. + + + The operating system is Windows 95, Windows 98, or Windows ME. + + + The operating system supports demand installation. + + + A custom action called from install script execution. + + + A custom action called from rollback execution script. + + + A custom action called from commit execution script. + + + + Installed state of a Component or Feature. + + + + The component is disabled. + + + The installation configuration data is corrupt. + + + The installation is suspended or in progress. + + + Component is set to run from source, but source is unavailable. + + + The buffer overflow is returned. + + + An invalid parameter was passed to the function. + + + An unrecognized product or feature name was passed to the function. + + + The component is broken. + + + The feature is advertised. + + + The component is being removed. In action state and not settable. + + + The component is not installed, or action state is absent but clients remain. + + + The component is installed on the local drive. + + + The component will run from the source, CD, or network. + + + The component will be installed in the default location: local or source. + + + + Specifies the type of installation for . + + + + Searches system for products to patch. + + + Indicates a administrative installation. + + + Indicates a particular instance. + + + + Level of the installation user interface, specified with + . + + + + Does not change UI level. + + + Uses Default UI level. + + + Silent installation. + + + Simple progress and error handling. + + + Authored UI, wizard dialogs suppressed. + + + Authored UI with wizards, progress, and errors. + + + + When combined with the value, the installer does not display + the cancel button in the progress dialog. + + + + + When combined with the value, the installer displays progress + dialog boxes but does not display any modal dialog boxes or error dialog boxes. + + + + + When combined with another value, the installer displays a modal dialog + box at the end of a successful installation or if there has been an error. + No dialog box is displayed if the user cancels. + + + + + Forces display of the source resolution dialog even if the UI is otherwise silent. + + + + + Specifies a return status value for message handlers. These values are returned by + , , and . + + + + An error was found in the message handler. + + + No action was taken. + + + IDOK + + + IDCANCEL + + + IDABORT + + + IDRETRY + + + IDIGNORE + + + IDYES + + + IDNO + + + + Specifies the different patch states for . + + + + Invalid value. + + + Patches applied to a product. + + + Patches that are superseded by other patches. + + + Patches that are obsolesced by other patches. + + + Patches that are registered to a product but not applied. + + + All valid patch states. + + + + Specifies the reinstall mode for or . + + + + Reinstall only if file is missing. + + + Reinstall if file is missing, or older version. + + + Reinstall if file is missing, or equal or older version. + + + Reinstall if file is missing, or not exact version. + + + Checksum executables, reinstall if missing or corrupt. + + + Reinstall all files, regardless of version. + + + Insure required machine reg entries. + + + Insure required user reg entries. + + + Validate shortcuts items. + + + Use re-cache source install package. + + + + Attributes for methods. + + + + No attributes. + + + Request that the Windows Installer not shutdown the embedded UI until the transaction is complete. + + + Request that the Windows Installer transfer the embedded UI from the original installation. + + + + Transform error conditions available for or + . + + + + No error conditions. + + + Adding a row that already exists. + + + Deleting a row that doesn't exist. + + + Adding a table that already exists. + + + Deleting a table that doesn't exist. + + + Updating a row that doesn't exist. + + + Transform and database code pages do not match and neither code page is neutral. + + + Create the temporary _TransformView table when applying the transform. + + + + Transform validation flags available for . + + + + Validate no properties. + + + Default language must match base database. + + + Product must match base database. + + + Check major version only. + + + Check major and minor versions only. + + + Check major, minor, and update versions. + + + Installed version < base version. + + + Installed version <= base version. + + + Installed version = base version. + + + Installed version >= base version. + + + Installed version > base version. + + + UpgradeCode must match base database. + + + + Specifies the installation context for s, + es, and + + + + + Not installed. + + + User managed install context. + + + User non-managed context. + + + Per-machine context. + + + All contexts, or all valid values. + + + All user-managed contexts. + + + + Defines the type of error encountered by the , , + or methods of the class. + + + + No error. + + + The new record duplicates primary keys of the existing record in a table. + + + There are no null values allowed, or the column is about to be deleted but is referenced by another row. + + + The corresponding record in a foreign table was not found. + + + The data is greater than the maximum value allowed. + + + The data is less than the minimum value allowed. + + + The data is not a member of the values permitted in the set. + + + An invalid version string was supplied. + + + The case was invalid. The case must be all uppercase or all lowercase. + + + An invalid GUID was supplied. + + + An invalid wildcard file name was supplied, or the use of wildcards was invalid. + + + An invalid identifier was supplied. + + + Invalid language IDs were supplied. + + + An invalid file name was supplied. + + + An invalid path was supplied. + + + An invalid conditional statement was supplied. + + + An invalid format string was supplied. + + + An invalid template string was supplied. + + + An invalid string was supplied in the DefaultDir column of the Directory table. + + + An invalid registry path string was supplied. + + + An invalid string was supplied in the CustomSource column of the CustomAction table. + + + An invalid property string was supplied. + + + The _Validation table is missing a reference to a column. + + + The category column of the _Validation table for the column is invalid. + + + The table in the Keytable column of the _Validation table was not found or loaded. + + + The value in the MaxValue column of the _Validation table is less than the value in the MinValue column. + + + An invalid cabinet name was supplied. + + + An invalid shortcut target name was supplied. + + + The string is too long for the length specified by the column definition. + + + An invalid localization attribute was supplied. (Primary keys cannot be localized.) + + + + Specifies the modify mode for . + + + + + Refreshes the information in the supplied record without changing the position + in the result set and without affecting subsequent fetch operations. + + + + Refreshes the data in a Record. + + + Inserts a Record into the view. + + + Updates the View with new data from the Record. + + + Updates or inserts a Record into the View. + + + Updates or deletes and inserts a Record into the View. + + + Inserts or validates a record. + + + Deletes a Record from the View. + + + Inserts a Record into the View. The inserted data is not persistent. + + + Validates a record. + + + Validates a new record. + + + Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. + + + Validates a record that will be deleted later. + + + + Base class for Windows Installer exceptions. + + + + + Creates a new InstallerException with a specified error message and a reference to the + inner exception that is the cause of this exception. + + The message that describes the error. + The exception that is the cause of the current exception. If the + innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + is raised in a catch block that handles the inner exception. + + + + Creates a new InstallerException with a specified error message. + + The message that describes the error. + + + + Creates a new InstallerException. + + + + + Initializes a new instance of the InstallerException class with serialized data. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + Sets the SerializationInfo with information about the exception. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + Gets extended information about the error, or null if no further information + is available. + + A Record object. Field 1 of the Record contains the installer + message code. Other fields contain data specific to the particular error. +

+ If the record is passed to , it is formatted + by looking up the string in the current database. If there is no installation + session, the format string may be obtained by a query on the Error table using + the error code, followed by a call to . + Alternatively, the standard MSI message can by retrieved by calling the + method. +

+ The following methods and properties may report extended error data: + + (constructor) + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + (constructor) + . + . + . + . + . + . + . + . + . + +

+ The Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiGetLastErrorRecord +

+
+ + + Gets the system error code that resulted in this exception, or 0 if not applicable. + + + + + Gets a message that describes the exception. This message may contain detailed + formatted error data if it was available. + + + + + User Canceled the installation. + + + + + Creates a new InstallCanceledException with a specified error message and a reference to the + inner exception that is the cause of this exception. + + The message that describes the error. + The exception that is the cause of the current exception. If the + innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + is raised in a catch block that handles the inner exception. + + + + Creates a new InstallCanceledException with a specified error message. + + The message that describes the error. + + + + Creates a new InstallCanceledException. + + + + + Initializes a new instance of the InstallCanceledException class with serialized data. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + A bad SQL query string was passed to or . + + + + + Creates a new BadQuerySyntaxException with a specified error message and a reference to the + inner exception that is the cause of this exception. + + The message that describes the error. + The exception that is the cause of the current exception. If the + innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + is raised in a catch block that handles the inner exception. + + + + Creates a new BadQuerySyntaxException with a specified error message. + + The message that describes the error. + + + + Creates a new BadQuerySyntaxException. + + + + + Initializes a new instance of the BadQuerySyntaxException class with serialized data. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + A method was called on an invalid installer handle. The handle may have been already closed. + + + + + Creates a new InvalidHandleException with a specified error message and a reference to the + inner exception that is the cause of this exception. + + The message that describes the error. + The exception that is the cause of the current exception. If the + innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + is raised in a catch block that handles the inner exception. + + + + Creates a new InvalidHandleException with a specified error message. + + The message that describes the error. + + + + Creates a new InvalidHandleException. + + + + + Initializes a new instance of the InvalidHandleException class with serialized data. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + A failure occurred when executing . The exception may contain + details about the merge conflict. + + + + + Creates a new MergeException with a specified error message and a reference to the + inner exception that is the cause of this exception. + + The message that describes the error. + The exception that is the cause of the current exception. If the + innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + is raised in a catch block that handles the inner exception. + + + + Creates a new MergeException with a specified error message. + + The message that describes the error. + + + + Creates a new MergeException. + + + + + Initializes a new instance of the MergeException class with serialized data. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + Sets the SerializationInfo with information about the exception. + + The SerializationInfo that holds the serialized object data about the exception being thrown. + The StreamingContext that contains contextual information about the source or destination. + + + + Gets the number of merge conflicts in each table, corresponding to the tables returned by + . + + + + + Gets the list of tables containing merge conflicts. + + + + + Gets a message that describes the merge conflits. + + + + + Defines a callback function that the installer calls for progress notification and error messages. + + + + + [MSI 3.1] Defines a callback function that the installer calls for record-based progress notification and error messages. + + + + + Provides static methods for installing and configuring products and patches. + + + + + Enables an external user-interface handler. This external UI handler is called before the + normal internal user-interface handler. The external UI handler has the option to suppress + the internal UI by returning a non-zero value to indicate that it has handled the messages. + + A callback delegate that handles the UI messages + Specifies which messages to handle using the external message handler. + If the external handler returns a non-zero result, then that message will not be sent to the UI, + instead the message will be logged if logging has been enabled. + The previously set external handler, or null if there was no previously set handler +

+ To restore the previous UI handler, a second call is made to SetExternalUI using the + ExternalUIHandler returned by the first call to SetExternalUI and specifying + as the message filter. +

+ The external user interface handler does not have full control over the external user + interface unless is called with the uiLevel parameter set to + . If SetInternalUI is not called, the internal user + interface level defaults to . As a result, any message not + handled by the external user interface handler is handled by Windows Installer. The initial + "Preparing to install..." dialog always appears even if the external user interface + handler handles all messages. +

+ SetExternalUI should only be called from a bootstrapping application. You cannot call + it from a custom action +

+ Win32 MSI API: + MsiSetExternalUI +

+
+ + + [MSI 3.1] Enables a record-based external user-interface handler. This external UI handler is called + before the normal internal user-interface handler. The external UI handler has the option to suppress + the internal UI by returning a non-zero value to indicate that it has handled the messages. + + A callback delegate that handles the UI messages + Specifies which messages to handle using the external message handler. + If the external handler returns a non-zero result, then that message will not be sent to the UI, + instead the message will be logged if logging has been enabled. + The previously set external handler, or null if there was no previously set handler +

+ To restore the previous UI handler, a second call is made to SetExternalUI using the + ExternalUIHandler returned by the first call to SetExternalUI and specifying + as the message filter. +

+ The external user interface handler does not have full control over the external user + interface unless is called with the uiLevel parameter set to + . If SetInternalUI is not called, the internal user + interface level defaults to . As a result, any message not + handled by the external user interface handler is handled by Windows Installer. The initial + "Preparing to install..." dialog always appears even if the external user interface + handler handles all messages. +

+ SetExternalUI should only be called from a bootstrapping application. You cannot call + it from a custom action +

+ Win32 MSI API: + MsiSetExternalUIRecord +

+
+ + + Enables the installer's internal user interface. Then this user interface is used + for all subsequent calls to user-interface-generating installer functions in this process. + + Specifies the level of complexity of the user interface + Handle to a window, which becomes the owner of any user interface created. + A pointer to the previous owner of the user interface is returned. + The previous user interface level +

+ Win32 MSI API: + MsiSetInternalUI +

+
+ + + Enables the installer's internal user interface. Then this user interface is used + for all subsequent calls to user-interface-generating installer functions in this process. + The owner of the user interface does not change. + + Specifies the level of complexity of the user interface + The previous user interface level +

+ Win32 MSI API: + MsiSetInternalUI +

+
+ + + Enables logging of the selected message type for all subsequent install sessions in + the current process space. + + One or more mode flags specifying the type of messages to log + Full path to the log file. A null path disables logging, + in which case the logModes paraneter is ignored. + an invalid log mode was specified + This method takes effect on any new installation processes. Calling this + method from within a custom action will not start logging for that installation. + + + + Enables logging of the selected message type for all subsequent install sessions in + the current process space. + + One or more mode flags specifying the type of messages to log + Full path to the log file. A null path disables logging, + in which case the logModes paraneter is ignored. + If true, the log lines will be appended to any existing file content. + If false, the log file will be truncated if it exists. The default is false. + If true, the log will be flushed after every line. + If false, the log will be flushed every 20 lines. The default is true. + an invalid log mode was specified +

+ This method takes effect on any new installation processes. Calling this + method from within a custom action will not start logging for that installation. +

+ Win32 MSI API: + MsiEnableLog +

+
+ + + increments the usage count for a particular feature and returns the installation state for + that feature. This method should be used to indicate an application's intent to use a feature. + + The product code of the product. + The feature to be used. + Must have the value . + The installed state of the feature. +

+ The UseFeature method should only be used on features known to be published. The application + should determine the status of the feature by calling either the FeatureState method or + Features method. +

+ Win32 MSI APIs: + MsiUseFeature, + MsiUseFeatureEx +

+
+ + + Opens an installer package for use with functions that access the product database and install engine, + returning an Session object. + + Path to the package + Specifies whether or not the create a Session object that ignores the + computer state and that is incapable of changing the current computer state. A value of false yields + the normal behavior. A value of true creates a "safe" Session object that cannot change of the current + machine state. + A Session object allowing access to the product database and install engine + The product could not be opened + The installer configuration data is corrupt +

+ Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a + custom action because the active installation is the only session allowed. +

+ A "safe" Session object ignores the current computer state when opening the package and prevents + changes to the current computer state. +

+ The Session object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI APIs: + MsiOpenPackage, + MsiOpenPackageEx +

+
+ + + Opens an installer package for use with functions that access the product database and install engine, + returning an Session object. + + Database used to create the session + Specifies whether or not the create a Session object that ignores the + computer state and that is incapable of changing the current computer state. A value of false yields + the normal behavior. A value of true creates a "safe" Session object that cannot change of the current + machine state. + A Session object allowing access to the product database and install engine + The product could not be opened + The installer configuration data is corrupt +

+ Note that only one Session object can be opened by a single process. OpenPackage cannot be used in a + custom action because the active installation is the only session allowed. +

+ A "safe" Session object ignores the current computer state when opening the package and prevents + changes to the current computer state. +

+ The Session object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI APIs: + MsiOpenPackage, + MsiOpenPackageEx +

+
+ + + Opens an installer package for an installed product using the product code. + + Product code of the installed product + A Session object allowing access to the product database and install engine, + or null if the specified product is not installed. + An unknown product was requested + The product could not be opened + The installer configuration data is corrupt +

+ Note that only one Session object can be opened by a single process. OpenProduct cannot be + used in a custom action because the active installation is the only session allowed. +

+ The Session object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiOpenProduct +

+
+ + + Gets the full component path, performing any necessary installation. This method prompts for source if + necessary and increments the usage count for the feature. + + Product code for the product that contains the feature with the necessary component + Feature ID of the feature with the necessary component + Component code of the necessary component + Installation mode; this can also include bits from + Path to the component +

+ Win32 MSI API: + MsiProvideComponent +

+
+ + + Gets the full component path for a qualified component that is published by a product and + performs any necessary installation. This method prompts for source if necessary and increments + the usage count for the feature. + + Specifies the component ID for the requested component. This may not be the + GUID for the component itself but rather a server that provides the correct functionality, as in the + ComponentId column of the PublishComponent table. + Specifies a qualifier into a list of advertising components (from PublishComponent Table). + Installation mode; this can also include bits from + Optional; specifies the product to match that has published the qualified component. + Path to the component +

+ Win32 MSI APIs: + MsiProvideQualifiedComponent + MsiProvideQualifiedComponentEx +

+
+ + + Gets the full path to a Windows Installer component containing an assembly. This method prompts for a source and + increments the usage count for the feature. + + Assembly name + Set to null for global assemblies. For private assemblies, set to the full path of the + application configuration file (.cfg file) or executable file (.exe) of the application to which the assembly + has been made private. + Installation mode; this can also include bits from + True if this is a Win32 assembly, false if it is a .NET assembly + Path to the assembly +

+ Win32 MSI API: + MsiProvideAssembly +

+
+ + + Installs files that are unexpectedly missing. + + Product code for the product that owns the component to be installed + Component to be installed + Specifies the way the component should be installed. + the user exited the installation +

+ Win32 MSI API: + MsiInstallMissingComponent +

+
+ + + Installs files that are unexpectedly missing. + + Product code for the product that owns the file to be installed + File to be installed + the user exited the installation +

+ Win32 MSI API: + MsiInstallMissingFile +

+
+ + + Reinstalls a feature. + + Product code for the product containing the feature to be reinstalled + Feature to be reinstalled + Reinstall modes + the user exited the installation +

+ Win32 MSI API: + MsiReinstallFeature +

+
+ + + Reinstalls a product. + + Product code for the product to be reinstalled + Reinstall modes + the user exited the installation +

+ Win32 MSI API: + MsiReinstallProduct +

+
+ + + Opens an installer package and initializes an install session. + + path to the patch package + command line property settings + There was an error installing the product +

+ To completely remove a product, set REMOVE=ALL in . +

+ This method displays the user interface with the current settings and + log mode. You can change user interface settings with the + and functions. You can set the log mode with the + function. +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI API: + MsiInstallProduct +

+
+ + + Installs or uninstalls a product. + + Product code of the product to be configured. + Specifies the default installation configuration of the + product. The parameter is ignored and all features + are installed if the parameter is set to any other + value than . This parameter must be either 0 + (install using authored feature levels), 65535 (install all features), or a value + between 0 and 65535 to install a subset of available features. + Specifies the installation state for the product. + Specifies the command line property settings. This should + be a list of the format Property=Setting Property=Setting. + There was an error configuring the product +

+ This method displays the user interface with the current settings and + log mode. You can change user interface settings with the + and functions. You can set the log mode with the + function. +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI APIs: + MsiConfigureProduct, + MsiConfigureProductEx +

+
+ + + Configures the installed state for a product feature. + + Product code of the product to be configured. + Specifies the feature ID for the feature to be configured. + Specifies the installation state for the feature. + There was an error configuring the feature +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI API: + MsiConfigureFeature +

+
+ + + For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes + an installation and sets the PATCH property to the path of the patch package. + + path to the patch package + optional command line property settings + There was an error applying the patch +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI API: + MsiApplyPatch +

+
+ + + For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes + an installation and sets the PATCH property to the path of the patch package. + + path to the patch package + path to the product to be patched, if installType + is set to + type of installation to patch + optional command line property settings + There was an error applying the patch +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI API: + MsiApplyPatch +

+
+ + + Removes one or more patches from a single product. To remove a patch from + multiple products, RemovePatches must be called for each product. + + List of patches to remove. Each patch can be specified by the GUID + of the patch or the full path to the patch package. + The ProductCode (GUID) of the product from which the patches + are removed. This parameter cannot be null. + optional command line property settings + There was an error removing the patches +

+ The and properties should be + tested after calling this method. +

+ Win32 MSI API: + MsiRemovePatches +

+
+ + + Determines which patches apply to a specified product MSI and in what sequence. + + Full path to an MSI file that is the target product + for the set of patches. + An array of strings specifying the patches to be checked. Each item + may be the path to an MSP file, the path an XML file, or just an XML blob. + Callback to be invoked for each inapplicable patch, reporting the + reason the patch is not applicable. This value may be left null if that information is not + desired. + An array of selected patch strings from , indicating + the set of applicable patches. The items are re-ordered to be in the best sequence. +

+ If an item in is a file path but does not end in .MSP or .XML, + it is assumed to be an MSP file. +

+ As this overload uses InstallContext.None, it does not consider the current state of + the system. +

+ Win32 MSI API: + MsiDetermineApplicablePatches +

+
+ + + Determines which patches apply to a specified product and in what sequence. If + the product is installed, this method accounts for patches that have already been applied to + the product and accounts for obsolete and superceded patches. + + The product that is the target for the set of patches. This may be + either a ProductCode (GUID) of a product that is currently installed, or the path to a an + MSI file. + An array of strings specifying the patches to be checked. Each item + may be the path to an MSP file, the path an XML file, or just an XML blob. + Callback to be invoked for each inapplicable patch, reporting the + reason the patch is not applicable. This value may be left null if that information is not + desired. + Specifies a security identifier (SID) of a user. This parameter restricts + the context of enumeration for this user account. This parameter cannot be the special SID + strings s-1-1-0 (everyone) or s-1-5-18 (local system). If is set to + or , then + must be null. For the current user context, + can be null and can be set to + or . + Restricts the enumeration to per-user-unmanaged, per-user-managed, + or per-machine context, or (if referring to an MSI) to no system context at all. This + parameter can be , , + , or . + An array of selected patch strings from , indicating + the set of applicable patches. The items are re-ordered to be in the best sequence. +

+ If an item in is a file path but does not end in .MSP or .XML, + it is assumed to be an MSP file. +

+ Passing an InstallContext of None only analyzes the MSI file; it does not consider the + current state of the system. You cannot use InstallContext.None with a ProductCode GUID. +

+ Win32 MSI APIs: + MsiDetermineApplicablePatches + MsiDeterminePatchSequence +

+
+ + + Applies one or more patches to products that are eligible to receive the patch. + For each product listed by the patch package as eligible to receive the patch, ApplyPatch invokes + an installation and sets the PATCH property to the path of the patch package. + + The set of patch packages to be applied. + Each item is the full path to an MSP file. + Provides the ProductCode of the product being patched. If this parameter + is null, the patches are applied to all products that are eligible to receive these patches. + optional command line property settings +

+ Win32 MSI API: + MsiApplyMultiplePatches +

+
+ + + Extracts information from a patch that can be used to determine whether the patch + applies on a target system. The method returns an XML string that can be provided to + + instead of the full patch file. + + Full path to the patch being queried. + XML string containing patch data. +

+ Win32 MSI API: + MsiExtractPatchXMLData +

+
+ + + [MSI 3.1] Migrates a user's application configuration data to a new SID. + + Previous user SID that data is to be migrated from + New user SID that data is to be migrated to +

+ Win32 MSI API: + MsiNotifySidChange +

+
+ + + Advertises a product to the local computer. + + Path to the package of the product being advertised + True if the product is user-assigned; false if it is machine-assigned. + Semi-colon delimited list of transforms to be applied. This parameter may be null. + The language to use if the source supports multiple languages + the specified package file does not exist + +

+ Win32 MSI APIs: + MsiAdvertiseProduct, + MsiAdvertiseProductEx +

+
+ + + Generates an advertise script. The method enables the installer to write to a + script the registry and shortcut information used to assign or publish a product. + + Path to the package of the product being advertised + path to script file to be created with the advertise information + Semi-colon delimited list of transforms to be applied. This parameter may be null. + The language to use if the source supports multiple languages + the specified package file does not exist + +

+ Win32 MSI APIs: + MsiAdvertiseProduct, + MsiAdvertiseProductEx +

+
+ + + Generates an advertise script. The method enables the installer to write to a + script the registry and shortcut information used to assign or publish a product. + + Path to the package of the product being advertised + path to script file to be created with the advertise information + Semi-colon delimited list of transforms to be applied. This parameter may be null. + The language to use if the source supports multiple languages + Targeted processor architecture. + True to install multiple instances through product code changing transform. + Advertises a new instance of the product. Requires that the parameter + includes the instance transform that changes the product code. + +

+ Win32 MSI APIs: + MsiAdvertiseProduct, + MsiAdvertiseProductEx +

+
+ + + Copies an advertise script file to the local computer. + + Path to a script file generated by + + Flags controlling advertisement + True if specified items are to be removed instead of being created +

+ The process calling this function must be running under the LocalSystem account. To advertise an + application for per-user installation to a targeted user, the thread that calls this function must + impersonate the targeted user. If the thread calling this function is not impersonating a targeted + user, the application is advertised to all users for installation with elevated privileges. +

+
+ + + Processes an advertise script file into the specified locations. + + Path to a script file generated by + + An optional path to a folder in which advertised icon files and transform + files are located. If this parameter is null, no icon or transform files are written. + True if shortcuts should be created + True if specified items are to be removed instead of created +

+ The process calling this function must be running under the LocalSystem account. To advertise an + application for per-user installation to a targeted user, the thread that calls this function must + impersonate the targeted user. If the thread calling this function is not impersonating a targeted + user, the application is advertised to all users for installation with elevated privileges. +

+ Win32 MSI API: + MsiProcessAdvertiseScript +

+
+ + + Gets product information for an installer script file. + + Path to a script file generated by + + ProductInstallation stub with advertise-related properties filled in. + An invalid product property was requested +

+ Only the following properties will be filled in in the returned object:

    +
  • +
  • +
  • +
  • +
  • +
Other properties will be null. +

+ Win32 MSI API: + MsiGetProductInfoFromScript +

+
+ + + Gets a Windows Installer error message in the system default language. + + The error number. + The message string, or null if the error message is not found. +

+ The returned string may have tokens such as [2] and [3] that are meant to be substituted + with context-specific values. +

+ Error numbers greater than 2000 refer to MSI "internal" errors, and are always + returned in English. +

+
+ + + Gets a Windows Installer error message in a specified language. + + The error number. + The locale for the message. + The message string, or null if the error message or locale is not found. +

+ The returned string may have tokens such as [2] and [3] that are meant to be substituted + with context-specific values. +

+ Error numbers greater than 2000 refer to MSI "internal" errors, and are always + returned in English. +

+
+ + + Gets a formatted Windows Installer error message in the system default language. + + Error record containing the error number in the first field, and + error-specific parameters in the other fields. + The message string, or null if the error message is not found. +

+ Error numbers greater than 2000 refer to MSI "internal" errors, and are always + returned in English. +

+
+ + + Gets a formatted Windows Installer error message in a specified language. + + Error record containing the error number in the first field, and + error-specific parameters in the other fields. + The locale for the message. + The message string, or null if the error message or locale is not found. +

+ Error numbers greater than 2000 refer to MSI "internal" errors, and are always + returned in English. +

+
+ + + Gets the version string of the path specified using the format that the installer + expects to find it in in the database. + + Path to the file + Version string in the "#.#.#.#" format, or an empty string if the file + does not contain version information + the file does not exist or could not be read +

+ Win32 MSI API: + MsiGetFileVersion +

+
+ + + Gets the language string of the path specified using the format that the installer + expects to find them in in the database. + + Path to the file + Language string in the form of a decimal language ID, or an empty string if the file + does not contain a language ID + the file does not exist or could not be read +

+ Win32 MSI API: + MsiGetFileVersion +

+
+ + + Gets a 128-bit hash of the specified file. + + Path to the file + Integer array of length 4 which receives the + four 32-bit parts of the hash value. + the file does not exist or + could not be read +

+ Win32 MSI API: + MsiGetFileHash +

+
+ + + Examines a shortcut and returns its product, feature name, and component if available. + + Full path to a shortcut + ShortcutTarget structure containing target product code, feature, and component code +

+ Win32 MSI API: + MsiGetShortcutTarget +

+
+ + + Verifies that the given file is an installation package. + + Path to the package + True if the file is an installation package; false otherwise. + the specified package file does not exist + the package file could not be opened +

+ Win32 MSI API: + MsiVerifyPackage +

+
+ + + [MSI 4.0] Gets the list of files that can be updated by one or more patches. + + ProductCode (GUID) of the product which is + the target of the patches + list of file paths of one or more patches to be + analyzed + List of absolute paths of files that can be updated when the + patches are applied on this system. +

+ Win32 MSI API: + MsiGetPatchFileList +

+
+ + + Indicates whether a system reboot is required after running an installation or configuration operation. + + + + + Indicates whether a system reboot has been initiated after running an installation or configuration operation. + + + + + Gets the current version of the installer. + + + + + Accessor for information about features within the context of an installation session. + + + + + Checks if the collection contains a feature. + + name of the feature + true if the feature is in the collection, else false + + + + Copies the features into an array. + + array that receives the features + offset into the array + + + + Enumerates the features in the collection. + + an enumerator over all features in the collection + + + + Gets information about a feature within the context of an installation session. + + name of the feature + feature object + + + + Gets the number of features defined for the product. + + + + + Provides access to information about a feature within the context of an installation session. + + + + + Calculates the disk space required by the feature and its selected children and parent features. + + If true, the parent features are included in the cost. + If true, the child features are included in the cost. + Specifies the installation state. + The disk space requirement in bytes. +

+ Win32 MSI API: + MsiGetFeatureCost +

+
+ + + Gets the name of the feature (primary key in the Feature table). + + + + + Gets the current install state of the feature. + + the Session handle is invalid + an unknown feature was requested +

+ Win32 MSI API: + MsiGetFeatureState +

+
+ + + Gets or sets the action state of the feature. + + the Session handle is invalid + an unknown feature was requested +

+ When changing the feature action, the action state of all the Components linked to the changed + Feature records are also updated appropriately, based on the new feature Select state. + All Features can be configured at once by specifying the keyword ALL instead of a specific feature name. +

+ Win32 MSI APIs: + MsiGetFeatureState, + MsiSetFeatureState +

+
+ + + Gets a list of valid installation states for the feature. + + the Session handle is invalid + an unknown feature was requested +

+ Win32 MSI API: + MsiGetFeatureValidStates +

+
+ + + Gets or sets the attributes of the feature. + + the Session handle is invalid + an unknown feature was requested +

+ Win32 MSI APIs: + MsiGetFeatureInfo, + MsiSetFeatureAttributes +

+ Since the lpAttributes paramter of + MsiGetFeatureInfo + does not contain an equivalent flag for , this flag will + not be retrieved. +

+ Since the dwAttributes parameter of + MsiSetFeatureAttributes + does not contain an equivalent flag for , the presence + of this flag will be ignored. +

+
+ + + Gets the title of the feature. + + the Session handle is invalid + an unknown feature was requested +

+ Win32 MSI API: + MsiGetFeatureInfo +

+
+ + + Gets the description of the feature. + + the Session handle is invalid + an unknown feature was requested +

+ Win32 MSI API: + MsiGetFeatureInfo +

+
+ + + Represents an instance of a feature of an installed product. + + + + + Creates a new FeatureInstallation instance for a feature of a product. + + feature name + ProductCode GUID + + + + Gets the name of the feature. + + + + + Gets the installed state of the feature. + +

+ Win32 MSI API: + MsiQueryFeatureState +

+
+ + + Gets the parent of the feature, or null if the feature has no parent (it is a root feature). + + + Invocation of this property may be slightly costly for products with many features, + because it involves an enumeration of all the features in the product. + + + + + Gets the usage metrics for the feature. + +

+ If no usage metrics are recorded, the value is 0. +

+ Win32 MSI API: + MsiGetFeatureUsage +

+
+ + + Holds data about the usage of a feature. + + + + + Gets count of the number of times the feature has been used. + + + + + Gets the date the feature was last used. + + + + + [MSI 4.5] Interface for an embedded external user interface for an installation. + + + Classes which implement this interface must have a public constructor that takes no parameters. + + + + + Initializes the embedded UI. + + Handle to the installer which can be used to get and set properties. + The handle is only valid for the duration of this method call. + Path to the directory that contains all the files from the MsiEmbeddedUI table. + On entry, contains the current UI level for the installation. After this + method returns, the installer resets the UI level to the returned value of this parameter. + True if the embedded UI was successfully initialized; false if the installation + should continue without the embedded UI. + The installation was canceled by the user. + The embedded UI failed to initialize and + causes the installation to fail. +

+ Win32 MSI API: + InitializeEmbeddedUI +

+
+ + + Processes information and progress messages sent to the user interface. + + Message type. + Record that contains message data. + Message box buttons. + Message box icon. + Message box default button. + Result of processing the message. +

+ Win32 MSI API: + EmbeddedUIHandler +

+
+ + + Shuts down the embedded UI at the end of the installation. + + + If the installation was canceled during initialization, this method will not be called. + If the installation was canceled or failed at any later point, this method will be called at the end. +

+ Win32 MSI API: + ShutdownEmbeddedUI +

+
+ + + Subclasses of this abstract class represent a unique instance of a + registered product or patch installation. + + + + + Gets the user security identifier (SID) under which this product or patch + installation is available. + + + + + Gets the user context of this product or patch installation. + + + + + Gets the source list of this product or patch installation. + + + + + Gets a value indicating whether this product or patch is installed on the current system. + + + + + Gets a property about the product or patch installation. + + Name of the property being retrieved. + + + + + Represents a per-drive disk space cost for an installation. + + + + + Creates a new InstallCost object. + + name of the drive this cost data applies to + installation cost on this drive, as a number of bytes + temporary disk space required on this drive, as a number of bytes + + + + The name of the drive this cost data applies to. + + + + + The installation cost on this drive, as a number of bytes. + + + + + The temporary disk space required on this drive, as a number of bytes. + +

+ This temporary space requirement is space needed only for the duration + of the installation, over the final footprint on disk. +

+
+ + + Receives an exception from + + indicating the reason a particular patch is not applicable to a product. + + MSP file path, XML file path, or XML blob that was passed to + + exception indicating the reason the patch is not applicable +

+ If is an or subclass, then + its and + properties will indicate a more specific reason the patch was not applicable. +

+ The could also be a FileNotFoundException if the + patch string was a file path. +

+
+ + + [MSI 4.5] Handle to a multi-session install transaction. + +

+ Win32 MSI APIs: + MsiBeginTransaction + MsiJoinTransaction + MsiEndTransaction +

+
+ + + [MSI 4.5] Begins transaction processing of a multi-package installation. + + Name of the multi-package installation. + Select optional behavior when beginning the transaction. + The transaction could not be initialized. +

+ Win32 MSI API: + MsiBeginTransaction +

+
+ + + Internal constructor. + + + The second parameter is an array in order to receive multiple values from the initialization method. + + + + + Creates a new Transaction object from an integer handle. + + Integer transaction handle + true to close the handle when this object is disposed + + + + Makes the current process the owner of the multi-package installation transaction. + + Select optional behavior when joining the transaction. + The transaction handle is not valid. + The transaction could not be joined. +

+ Win32 MSI API: + MsiJoinTransaction +

+
+ + + Ends the install transaction and commits all changes to the system belonging to the transaction. + + The transaction could not be committed. +

+ Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language + runtime assemblies. Deletes the rollback script, and after using this option, the transaction's + changes can no longer be undone with a Rollback Installation. +

+ This method can only be called by the current owner of the transaction. +

+ Win32 MSI API: + MsiEndTransaction +

+
+ + + Ends the install transaction and undoes changes to the system belonging to the transaction. + + The transaction could not be rolled back. +

+ This method can only be called by the current owner of the transaction. +

+ Win32 MSI API: + MsiEndTransaction +

+
+ + + Gets the name of the transaction. + + + + + Notifies listeners when the process that owns the transaction changed. + + + + + Represents a media disk source of a product or a patch. + + + + + Creates a new media disk. + + + + + + + + Gets or sets the disk id of the media disk. + + + + + Gets or sets the volume label of the media disk. + + + + + Gets or sets the disk prompt of the media disk. + + + + + The Patch object represents a unique instance of a patch that has been + registered or applied. + + + + + Enumerates patch installations based on certain criteria. + + PatchCode (GUID) of the patch to be enumerated. Only + instances of patches within the scope of the context specified by the + and parameters will be + enumerated. This parameter may be set to null to enumerate all patches in the specified + context. + ProductCode (GUID) product whose patches are to be + enumerated. If non-null, patch enumeration is restricted to instances of this product + within the specified context. If null, the patches for all products under the specified + context are enumerated. + Specifies a security identifier (SID) that restricts the context + of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts + enumeration to the current user or any user in the system. The special SID string + s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter + can be set to null to restrict the enumeration scope to the current user. When + is set to the machine context only, + must be null. + Specifies the user context. + The of patches to return. +

+ Win32 MSI APIs: + MsiEnumPatchesEx +

+
+ + + Creates a new object for accessing information about a patch installation on the current system. + + Patch code (GUID) of the patch. + ProductCode (GUID) the patch has been applied to. + This parameter may be null for patches that are registered only and not yet + applied to any product. +

+ All available user contexts will be queried. +

+
+ + + Creates a new object for accessing information about a patch installation on the current system. + + Registered patch code (GUID) of the patch. + ProductCode (GUID) the patch has been applied to. + This parameter may be null for patches that are registered only and not yet + applied to any product. + The specific user, when working in a user context. This + parameter may be null to indicate the current user. The parameter must be null + when working in a machine context. + The user context. The calling process must have administrative + privileges to get information for a product installed for a user other than the + current user. +

+ If the is null, the Patch object may + only be used to read and update the patch's SourceList information. +

+
+ + + Enumerates all patch installations on the system. + + Enumeration of patch objects. +

+ Win32 MSI API: + MsiEnumPatches +

+
+ + + Gets the patch code (GUID) of the patch. + + + + + Gets the ProductCode (GUID) of the product. + + + + + Gets a value indicating whether this patch is currently installed. + + + + + Gets a value indicating whether this patch is marked as obsolte. + + + + + Gets a value indicating whether this patch is present but has been + superseded by a more recent installed patch. + + + + + Gets the installation state of this instance of the patch. + + An unknown patch was requested + The installer configuration data is corrupt + + + + Gets the cached patch file that the product uses. + + + + + Gets the set of patch transforms that the last patch + installation applied to the product. + +

+ This value may not be available for per-user, non-managed applications + if the user is not logged on. +

+
+ + + Gets the date and time when the patch is applied to the product. + + + + + True patch is marked as possible to uninstall from the product. + +

+ Even if this property is true, the installer can still block the + uninstallation if this patch is required by another patch that + cannot be uninstalled. +

+
+ + + Get the registered display name for the patch. + + + + + Gets the registered support information URL for the patch. + + + + + Gets information about a specific patch installation. + + The property being retrieved; see remarks for valid properties. + The property value, or an empty string if the property is not set for the patch. + An unknown patch or property was requested + The installer configuration data is corrupt +

+ Win32 MSI APIs: + MsiGetPatchInfo, + MsiGetPatchInfoEx +

+
+ + + Represents a unique instance of a product that + is either advertised, installed or unknown. + + + + + Gets the set of all products with a specified upgrade code. This method lists the + currently installed and advertised products that have the specified UpgradeCode + property in their Property table. + + Upgrade code of related products + Enumeration of product codes +

+ Win32 MSI API: + MsiEnumRelatedProducts +

+
+ + + Enumerates product installations based on certain criteria. + + ProductCode (GUID) of the product instances to be enumerated. Only + instances of products within the scope of the context specified by the + and parameters will be + enumerated. This parameter may be set to null to enumerate all products in the specified + context. + Specifies a security identifier (SID) that restricts the context + of enumeration. A SID value other than s-1-1-0 is considered a user SID and restricts + enumeration to the current user or any user in the system. The special SID string + s-1-1-0 (Everyone) specifies enumeration across all users in the system. This parameter + can be set to null to restrict the enumeration scope to the current user. When + is set to the machine context only, + must be null. + Specifies the user context. + An enumeration of product objects for enumerated product instances. +

+ Win32 MSI API: + MsiEnumProductsEx +

+
+ + + Creates a new object for accessing information about a product installation on the current system. + + ProductCode (GUID) of the product. +

+ All available user contexts will be queried. +

+
+ + + Creates a new object for accessing information about a product installation on the current system. + + ProductCode (GUID) of the product. + The specific user, when working in a user context. This + parameter may be null to indicate the current user. The parameter must be null + when working in a machine context. + The user context. The calling process must have administrative + privileges to get information for a product installed for a user other than the + current user. + + + + Gets the installed state for a product feature. + + The feature being queried; identifier from the + Feature table + Installation state of the feature for the product instance: either + , , + or . +

+ Win32 MSI APIs: + MsiQueryFeatureState, + MsiQueryFeatureStateEx +

+
+ + + Gets the installed state for a product component. + + The component being queried; GUID of the component + as found in the ComponentId column of the Component table. + Installation state of the component for the product instance: either + or . +

+ Win32 MSI API: + MsiQueryComponentState +

+
+ + + Obtains and stores the user information and product ID from an installation wizard. + +

+ This method is typically called by an application during the first run of the application. The application + first gets the or . + If those properties are missing, the application calls CollectUserInfo. + CollectUserInfo opens the product's installation package and invokes a wizard sequence that collects + user information. Upon completion of the sequence, user information is registered. Since this API requires + an authored user interface, the user interface level should be set to full by calling + as . +

+ The CollectUserInfo method invokes a FirstRun dialog from the product installation database. +

+ Win32 MSI API: + MsiCollectUserInfo +

+
+ + + Some products might write some invalid/nonstandard version strings to the registry. + This method tries to get the best data it can. + + Version string retrieved from the registry. + Version object, or null if the version string is completely invalid. + + + + Enumerates all product installations on the system. + + An enumeration of product objects. +

+ Win32 MSI API: + MsiEnumProducts, +

+
+ + + Gets the set of published features for the product. + + Enumeration of published features for the product. + The installer configuration data is corrupt +

+ Because features are not ordered, any new feature has an arbitrary index, meaning + this property can return features in any order. +

+ Win32 MSI API: + MsiEnumFeatures +

+
+ + + Gets the ProductCode (GUID) of the product. + + + + + Gets a value indicating whether this product is installed on the current system. + + + + + Gets a value indicating whether this product is advertised on the current system. + + + + + Checks whether the product is installed with elevated privileges. An application is called + a "managed application" if elevated (system) privileges are used to install the application. + + True if the product is elevated; false otherwise +

+ Note that this property does not take into account policies such as AlwaysInstallElevated, + but verifies that the local system owns the product's registry data. +

+
+ + + Gets the source list of this product installation. + + + + + The support link. + + + + + The support telephone. + + + + + Date and time the product was installed. + + + + + The installed product name. + + + + + The installation location. + + + + + The installation source. + + + + + The local cached package. + + + + + The publisher. + + + + + URL about information. + + + + + The URL update information. + + + + + The product version. + + + + + The product identifier. + +

+ For more information, see + ProductID +

+
+ + + The company that is registered to use the product. + + + + + The owner who is registered to use the product. + + + + + Transforms. + + + + + Product language. + + + + + Human readable product name. + + + + + True if the product is advertised per-machine; + false if it is per-user or not advertised. + + + + + Identifier of the package that a product is installed from. + + + + + Version of the advertised product. + + + + + Primary icon for the package. + + + + + Name of the installation package for the advertised product. + + + + + True if the advertised product can be serviced by + non-administrators without elevation. + + + + + Gets information about an installation of a product. + + Name of the property being retrieved. + An unknown product or property was requested + The installer configuration data is corrupt +

+ Win32 MSI APIs: + MsiGetProductInfo, + MsiGetProductInfoEx +

+
+ + + The Record object is a container for holding and transferring a variable number of values. + Fields within the record are numerically indexed and can contain strings, integers, streams, + and null values. Record fields are indexed starting with 1. Field 0 is a special format field. + +

+ Most methods on the Record class have overloads that allow using either a number + or a name to designate a field. However note that field names only exist when the + Record is directly returned from a query on a database. For other records, attempting + to access a field by name will result in an InvalidOperationException. +

+
+ + + Creates a new record object with the requested number of fields. + + Required number of fields, which may be 0. + The maximum number of fields in a record is limited to 65535. +

+ The Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiCreateRecord +

+
+ + + Creates a new record object, providing values for an arbitrary number of fields. + + The values of the record fields. The parameters should be of type Int16, Int32 or String +

+ The Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiCreateRecord +

+
+ + + Creates a new Record object from an integer record handle. + +

+ This method is only provided for interop purposes. A Record object + should normally be obtained by calling + other methods. +

The handle will be closed when this object is disposed or finalized.

+

+ Integer record handle + true to close the handle when this object is disposed or finalized +
+ + + Sets all fields in a record to null. + +

+ Win32 MSI API: + MsiRecordClearData +

+
+ + + Reports whether a record field is null. + + Specifies the field to check. + True if the field is null, false otherwise. + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordIsNull +

+
+ + + Reports whether a record field is null. + + Specifies the field to check. + True if the field is null, false otherwise. + The field name does not match any + of the named fields in the Record. + + + + Gets the length of a record field. The count does not include the terminating null. + + The field is less than 0 or greater than the + number of fields in the Record. +

+ The returned data size is 0 if the field is null, non-existent, + or an internal object pointer. The method also returns 0 if the handle is not a valid + Record handle. +

+ If the data is in integer format, the property returns 2 or 4. +

+ If the data is in string format, the property returns the character count + (not including the NULL terminator). +

+ If the data is in stream format, the property returns the byte count. +

+ Win32 MSI API: + MsiRecordDataSize +

+
+ + + Gets the length of a record field. The count does not include the terminating null. + + Specifies the field to check. + The field name does not match any + of the named fields in the Record. +

The returned data size is 0 if the field is null, non-existent, + or an internal object pointer. The method also returns 0 if the handle is not a valid + Record handle. +

+ If the data is in integer format, the property returns 2 or 4. +

+ If the data is in string format, the property returns the character count + (not including the NULL terminator). +

+ If the data is in stream format, the property returns the byte count. +

+
+ + + Gets a field value as an integer. + + Specifies the field to retrieve. + Integer value of the field, or 0 if the field is null. + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordGetInteger +

+ +
+ + + Gets a field value as an integer. + + Specifies the field to retrieve. + Integer value of the field, or 0 if the field is null. + The field name does not match any + of the named fields in the Record. + + + + + Gets a field value as an integer. + + Specifies the field to retrieve. + Integer value of the field, or null if the field is null. + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordGetInteger +

+ +
+ + + Gets a field value as an integer. + + Specifies the field to retrieve. + Integer value of the field, or null if the field is null. + The field name does not match any + of the named fields in the Record. + + + + + Sets the value of a field to an integer. + + Specifies the field to set. + new value of the field + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordSetInteger +

+ +
+ + + Sets the value of a field to an integer. + + Specifies the field to set. + new value of the field + The field name does not match any + of the named fields in the Record. + + + + + Sets the value of a field to a nullable integer. + + Specifies the field to set. + new value of the field + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordSetInteger +

+ +
+ + + Sets the value of a field to a nullable integer. + + Specifies the field to set. + new value of the field + The field name does not match any + of the named fields in the Record. + + + + + Gets a field value as a string. + + Specifies the field to retrieve. + String value of the field, or an empty string if the field is null. + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordGetString +

+
+ + + Gets a field value as a string. + + Specifies the field to retrieve. + String value of the field, or an empty string if the field is null. + The field name does not match any + of the named fields in the Record. + + + + Sets the value of a field to a string. + + Specifies the field to set. + new value of the field + The field is less than 0 or greater than the + number of fields in the Record. +

+ Win32 MSI API: + MsiRecordSetString +

+
+ + + Sets the value of a field to a string. + + Specifies the field to set. + new value of the field + The field name does not match any + of the named fields in the Record. + + + + Reads a record stream field into a file. + + Specifies the field of the Record to get. + Specifies the path to the file to contain the stream. + The field is less than 0 or greater than the + number of fields in the Record. + Attempt to extract a storage from a database open + in read-write mode, or from a database without an associated file path +

+ This method is capable of directly extracting substorages. To do so, first select both the + `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. + However, substorages may only be extracted from a database that is open in read-only mode. +

+ Win32 MSI API: + MsiRecordReadStream +

+
+ + + Reads a record stream field into a file. + + Specifies the field of the Record to get. + Specifies the path to the file to contain the stream. + The field name does not match any + of the named fields in the Record. + Attempt to extract a storage from a database open + in read-write mode, or from a database without an associated file path +

+ This method is capable of directly extracting substorages. To do so, first select both the + `Name` and `Data` column of the `_Storages` table, then get the stream of the `Data` field. + However, substorages may only be extracted from a database that is open in read-only mode. +

+
+ + + Gets a record stream field. + + Specifies the field of the Record to get. + A Stream that reads the field data. + The field is less than 0 or greater than the + number of fields in the Record. +

+ This method is not capable of reading substorages. To extract a substorage, + use . +

+ Win32 MSI API: + MsiRecordReadStream +

+
+ + + Gets a record stream field. + + Specifies the field of the Record to get. + A Stream that reads the field data. + The field name does not match any + of the named fields in the Record. +

+ This method is not capable of reading substorages. To extract a substorage, + use . +

+
+ + + Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. + + Specifies the field of the Record to set. + Specifies the path to the file containing the stream. + The field is less than 0 or greater than the + number of fields in the Record. +

+ The contents of the specified file are read into a stream object. The stream persists if + the Record is inserted into the Database and the Database is committed. +

+ To reset the stream to its beginning you must pass in null for filePath. + Do not pass an empty string, "", to reset the stream. +

+ Setting a stream with this method is more efficient than setting a field to a + FileStream object. +

+ Win32 MSI API: + MsiRecordsetStream +

+
+ + + Sets a record stream field from a file. Stream data cannot be inserted into temporary fields. + + Specifies the field name of the Record to set. + Specifies the path to the file containing the stream. + The field name does not match any + of the named fields in the Record. +

+ The contents of the specified file are read into a stream object. The stream persists if + the Record is inserted into the Database and the Database is committed. + To reset the stream to its beginning you must pass in null for filePath. + Do not pass an empty string, "", to reset the stream. +

+ Setting a stream with this method is more efficient than setting a field to a + FileStream object. +

+
+ + + Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. + + Specifies the field of the Record to set. + Specifies the stream data. + The field is less than 0 or greater than the + number of fields in the Record. +

+ The stream persists if the Record is inserted into the Database and the Database is committed. +

+ Win32 MSI API: + MsiRecordsetStream +

+
+ + + Sets a record stream field from a Stream object. Stream data cannot be inserted into temporary fields. + + Specifies the field name of the Record to set. + Specifies the stream data. + The field name does not match any + of the named fields in the Record. +

+ The stream persists if the Record is inserted into the Database and the Database is committed. +

+
+ + + Gets a formatted string representation of the Record. + + A formatted string representation of the Record. +

+ If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. +

+ Win32 MSI API: + MsiFormatRecord +

+ + +
+ + + Gets a formatted string representation of the Record, optionally using a Session to format properties. + + an optional Session instance that will be used to lookup any + properties in the Record's format string + A formatted string representation of the Record. +

+ If field 0 of the Record is set to a nonempty string, it is used to format the data in the Record. +

+ Win32 MSI API: + MsiFormatRecord +

+ + +
+ + + Gets a formatted string representation of the Record. + + String to be used to format the data in the Record, + instead of the Record's format string. + A formatted string representation of the Record. +

+ Win32 MSI API: + MsiFormatRecord +

+
+ + + Gets a formatted string representation of the Record, optionally using a Session to format properties. + + String to be used to format the data in the Record, + instead of the Record's format string. + an optional Session instance that will be used to lookup any + properties in the Record's format string + A formatted string representation of the Record. +

+ Win32 MSI API: + MsiFormatRecord +

+ + +
+ + + Gets the number of fields in a record. + +

+ Win32 MSI API: + MsiRecordGetFieldCount +

+
+ + + Gets or sets field 0 of the Record, which is the format string. + + + + + Gets or sets a record field value. + + Specifies the name of the field of the Record to get or set. + The name does not match any known field of the Record. +

+ When getting a field, the type of the object returned depends on the type of the Record field. + The object will be one of: Int16, Int32, String, Stream, or null. +

+ When setting a field, the type of the object provided will be converted to match the View + query that returned the record, or if Record was not returned from a view then the type of + the object provided will determine the type of the Record field. The object should be one of: + Int16, Int32, String, Stream, or null. +

+
+ + + Gets or sets a record field value. + + Specifies the field of the Record to get or set. + The field is less than 0 or greater than the + number of fields in the Record. +

+ Record fields are indexed starting with 1. Field 0 is a special format field. +

+ When getting a field, the type of the object returned depends on the type of the Record field. + The object will be one of: Int16, Int32, String, Stream, or null. If the Record was returned + from a View, the type will match that of the field from the View query. Otherwise, the type + will match the type of the last value set for the field. +

+ When setting a field, the type of the object provided will be converted to match the View + query that returned the Record, or if Record was not returned from a View then the type of + the object provided will determine the type of the Record field. The object should be one of: + Int16, Int32, String, Stream, or null. +

+ The type-specific getters and setters are slightly more efficient than this property, since + they don't have to do the extra work to infer the value's type every time. +

+ Win32 MSI APIs: + MsiRecordGetInteger, + MsiRecordGetString, + MsiRecordSetInteger, + MsiRecordSetString +

+
+ + + Assigns ID numbers to the MSI APIs that are remotable. + +

+ This enumeration MUST stay in sync with the + unmanaged equivalent in RemoteMsiSession.h! +

+
+ + + Defines the signature of the native function + in SfxCA.dll that implements the remoting call. + + + + + Redirects native API calls to either the normal NativeMethods class + or to out-of-proc calls via the remoting channel. + + + + + Checks if the current process is using remoting to access the + MSI session and database APIs. + + + + + Sets a delegate that is used to make remote API calls. + +

+ The implementation of this delegate is provided by the + custom action host DLL. +

+
+ + + The Session object controls the installation process. It opens the + install database, which contains the installation tables and data. + +

+ This object is associated with a standard set of action functions, + each performing particular operations on data from one or more tables. Additional + custom actions may be added for particular product installations. The basic engine + function is a sequencer that fetches sequential records from a designated sequence + table, evaluates any specified condition expression, and executes the designated + action. Actions not recognized by the engine are deferred to the UI handler object + for processing, usually dialog box sequences. +

+ Note that only one Session object can be opened by a single process. +

+
+ + + Creates a new Session object from an integer session handle. + + Integer session handle + true to close the handle when this object is disposed or finalized +

+ This method is only provided for interop purposes. A Session object + should normally be obtained by calling + or . +

+
+ + + Performs any enabled logging operations and defers execution to the UI handler + object associated with the engine. + + Type of message to be processed + Contains message-specific fields + A message-dependent return value + the Session or Record handle is invalid + an invalid message kind is specified + the user exited the installation + the message-handler failed for an unknown reason +

+ Logging may be selectively enabled for the various message types. + See the method. +

+ If record field 0 contains a formatting string, it is used to format the data in + the other fields. Else if the message is an error, warning, or user message, an attempt + is made to find a message template in the Error table for the current database using the + error number found in field 1 of the record for message types and return values. +

+ The parameter may also include message-box flags from + the following enumerations: System.Windows.Forms.MessageBoxButtons, + System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxIcon. These + flags can be combined with the InstallMessage with a bitwise OR. +

+ Note, this method never returns Cancel or Error values. Instead, appropriate + exceptions are thrown in those cases. +

+ Win32 MSI API: + MsiProcessMessage +

+
+ + + Writes a message to the log, if logging is enabled. + + The line to be written to the log +

+ Win32 MSI API: + MsiProcessMessage +

+
+ + + Writes a formatted message to the log, if logging is enabled. + + The line to be written to the log, containing 0 or more format specifiers + An array containing 0 or more objects to be formatted +

+ Win32 MSI API: + MsiProcessMessage +

+
+ + + Evaluates a logical expression containing symbols and values. + + conditional expression + The result of the condition evaluation + the Session handle is invalid + the condition is null or empty + the conditional expression is invalid +

+ Win32 MSI API: + MsiEvaluateCondition +

+
+ + + Evaluates a logical expression containing symbols and values, specifying a default + value to be returned in case the condition is empty. + + conditional expression + value to return if the condition is empty + The result of the condition evaluation + the Session handle is invalid + the conditional expression is invalid +

+ Win32 MSI API: + MsiEvaluateCondition +

+
+ + + Formats a string containing installer properties. + + A format string containing property tokens + A formatted string containing property data + the Record handle is invalid +

+ Win32 MSI API: + MsiFormatRecord +

+
+ + + Returns a formatted string from record data. + + Record object containing a template and data to be formatted. + The template string must be set in field 0 followed by any referenced data parameters. + A formatted string containing the record data + the Record handle is invalid +

+ Win32 MSI API: + MsiFormatRecord +

+
+ + + Returns a formatted string from record data using a specified format. + + Record object containing a template and data to be formatted + Format string to be used instead of field 0 of the Record + A formatted string containing the record data + the Record handle is invalid +

+ Win32 MSI API: + MsiFormatRecord +

+
+ + + Retrieves product properties (not session properties) from the product database. + + Value of the property, or an empty string if the property is not set. +

+ Note this is not the correct method for getting ordinary session properties. For that, + see the indexer on the Session class. +

+ Win32 MSI API: + MsiGetProductProperty +

+
+ + + Checks to see if sufficient disk space is present for the current installation. + + True if there is sufficient disk space; false otherwise. +

+ Win32 MSI API: + MsiVerifyDiskSpace +

+
+ + + Gets the total disk space per drive required for the installation. + + A list of InstallCost structures, specifying the cost for each drive +

+ Win32 MSI API: + MsiEnumComponentCosts +

+
+ + + Gets the designated mode flag for the current install session. + + The type of mode to be checked. + The value of the designated mode flag. + the Session handle is invalid + an invalid mode flag was specified +

+ Note that only the following run modes are available to read from + a deferred custom action: + + + + +

+ Win32 MSI API: + MsiGetMode +

+
+ + + Sets the designated mode flag for the current install session. + + The type of mode to be set. + The desired value of the mode. + the Session handle is invalid + an invalid mode flag was specified + the mode cannot not be set +

+ Win32 MSI API: + MsiSetMode +

+
+ + + Gets the full path to the designated folder on the source media or server image. + + the folder was not found in the Directory table + the Session handle is invalid +

+ Win32 MSI API: + MsiGetSourcePath +

+
+ + + Gets the full path to the designated folder on the installation target drive. + + the folder was not found in the Directory table + the Session handle is invalid +

+ Win32 MSI API: + MsiGetTargetPath +

+
+ + + Sets the full path to the designated folder on the installation target drive. + + the folder was not found in the Directory table + the Session handle is invalid +

+ Setting the target path of a directory changes the path specification for the directory + in the in-memory Directory table. Also, the path specifications of all other path objects + in the table that are either subordinate or equivalent to the changed path are updated + to reflect the change. The properties for each affected path are also updated. +

+ If an error occurs in this function, all updated paths and properties revert to + their previous values. Therefore, it is safe to treat errors returned by this function + as non-fatal. +

+ Do not attempt to configure the target path if the components using those paths + are already installed for the current user or for a different user. Check the + ProductState property before setting the target path to determine if the product + containing this component is installed. +

+ Win32 MSI API: + MsiSetTargetPath +

+
+ + + Sets the install level for the current installation to a specified value and + recalculates the Select and Installed states for all features in the Feature + table. Also sets the Action state of each component in the Component table based + on the new level. + + New install level + the Session handle is invalid +

+ The SetInstallLevel method sets the following: + The installation level for the current installation to a specified value + The Select and Installed states for all features in the Feature table + The Action state of each component in the Component table, based on the new level + + If 0 or a negative number is passed in the ilnstallLevel parameter, + the current installation level does not change, but all features are still + updated based on the current installation level. +

+ Win32 MSI API: + MsiSetInstallLevel +

+
+ + + Executes a built-in action, custom action, or user-interface wizard action. + + Name of the action to execute. Case-sensitive. + the Session handle is invalid + the user exited the installation +

+ The DoAction method executes the action that corresponds to the name supplied. If the + name is not recognized by the installer as a built-in action or as a custom action in + the CustomAction table, the name is passed to the user-interface handler object, which + can invoke a function or a dialog box. If a null action name is supplied, the installer + uses the upper-case value of the ACTION property as the action to perform. If no property + value is defined, the default action is performed, defined as "INSTALL". +

+ Actions that update the system, such as the InstallFiles and WriteRegistryValues + actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction + is called from a custom action that is scheduled in the InstallExecuteSequence table + between the InstallInitialize and InstallFinalize actions. Actions that do not update the + system, such as AppSearch or CostInitialize, can be called. +

+ Win32 MSI API: + MsiDoAction +

+
+ + + Executes a built-in action, custom action, or user-interface wizard action. + + Name of the action to execute. Case-sensitive. + Optional data to be passed to a deferred custom action. + the Session handle is invalid + the user exited the installation +

+ The DoAction method executes the action that corresponds to the name supplied. If the + name is not recognized by the installer as a built-in action or as a custom action in + the CustomAction table, the name is passed to the user-interface handler object, which + can invoke a function or a dialog box. If a null action name is supplied, the installer + uses the upper-case value of the ACTION property as the action to perform. If no property + value is defined, the default action is performed, defined as "INSTALL". +

+ Actions that update the system, such as the InstallFiles and WriteRegistryValues + actions, cannot be run by calling MsiDoAction. The exception to this rule is if DoAction + is called from a custom action that is scheduled in the InstallExecuteSequence table + between the InstallInitialize and InstallFinalize actions. Actions that do not update the + system, such as AppSearch or CostInitialize, can be called. +

+ If the called action is a deferred, rollback, or commit custom action, then the supplied + will be available via the + property of that custom action's session. +

+ Win32 MSI API: + MsiDoAction +

+
+ + + Executes an action sequence described in the specified table. + + Name of the table containing the action sequence. + the Session handle is invalid + the user exited the installation +

+ This method queries the specified table, ordering the actions by the numbers in the Sequence column. + For each row retrieved, an action is executed, provided that any supplied condition expression does + not evaluate to FALSE. +

+ An action sequence containing any actions that update the system, such as the InstallFiles and + WriteRegistryValues actions, cannot be run by calling DoActionSequence. The exception to this rule is if + DoActionSequence is called from a custom action that is scheduled in the InstallExecuteSequence table + between the InstallInitialize and InstallFinalize actions. Actions that do not update the system, such + as AppSearch or CostInitialize, can be called. +

+ Win32 MSI API: + MsiSequence +

+
+ + + Implements formatting for data. + + Type of format object to get. + The the current instance, if is the same type + as the current instance; otherwise, null. + + + + Closes the session handle. Also closes the active database handle, if it is open. + After closing a handle, further method calls may throw an . + + If true, the method has been called directly + or indirectly by a user's code, so managed and unmanaged resources will + be disposed. If false, only unmanaged resources will be disposed. + + + + Throws an exception if the custom action is not able to access immedate session details. + + + + + Gets the Database for the install session. + + the Session handle is invalid + the Database cannot be accessed +

+ Normally there is no need to close this Database object. The same object can be + used throughout the lifetime of the Session, and it will be closed when the Session + is closed. +

+ Win32 MSI API: + MsiGetActiveDatabase +

+
+ + + Gets the numeric language ID used by the current install session. + +

+ Win32 MSI API: + MsiGetLanguage +

+
+ + + Gets or sets the string value of a named installer property, as maintained by the + Session object in the in-memory Property table, or, if it is prefixed with a percent + sign (%), the value of a system environment variable for the current process. + + the Session handle is invalid +

+ Win32 MSI APIs: + MsiGetProperty, + MsiSetProperty +

+
+ + + Gets an accessor for components in the current session. + + + + + Gets an accessor for features in the current session. + + + + + Gets custom action data for the session that was supplied by the caller. + + + + + + Gets the (short) list of properties that are available from non-immediate custom actions. + + + + + Holds information about the target of a shortcut file. + + + + + Tests whether two shortcut targets have the same product code, feature, and/or component code. + + The first shortcut target to compare. + The second shortcut target to compare. + True if all parts of the targets are the same, else false. + + + + Tests whether two shortcut targets have the same product code, feature, and/or component code. + + The first shortcut target to compare. + The second shortcut target to compare. + True if any parts of the targets are different, else false. + + + + Tests whether two shortcut targets have the same product code, feature, and/or component code. + + The shortcut target to compare to the current object. + True if is a shortcut target and all parts of the targets are the same, else false. + + + + Generates a hash code using all parts of the shortcut target. + + An integer suitable for hashing the shortcut target. + + + + Gets the target product code of the shortcut, or null if not available. + + + + + Gets the name of the target feature of the shortcut, or null if not available. + + + + + Gets the target component code of the shortcut, or null if not available. + + + + + A list of sources for an installed product or patch. + + + + + Adds a network or URL source to the source list of the installed product. + + Path to the source to be added. This parameter is + expected to contain only the path without the filename. +

+ If this method is called with a new source, the installer adds the source + to the end of the source list. +

+ If this method is called with a source already existing in the source + list, it has no effect. +

+ Win32 MSI APIs: + MsiSourceListAddSource, + MsiSourceListAddSourceEx +

+ +
+ + + Adds or reorders a network or URL source for the product or patch. + + Path to the source to be added. This parameter is + expected to contain only the path without the filename. + Specifies the priority order in which the source + will be inserted +

+ If this method is called with a new source and + is set to 0, the installer adds the source to the end of the source list. +

+ If this method is called with a source already existing in the source + list and is set to 0, the installer retains the + source's existing index. +

+ If the method is called with an existing source in the source list + and is set to a non-zero value, the source is + removed from its current location in the list and inserted at the position + specified by Index, before any source that already exists at that position. +

+ If the method is called with a new source and Index is set to a + non-zero value, the source is inserted at the position specified by + , before any source that already exists at + that position. The index value for all sources in the list after the + index specified by Index are updated to ensure unique index values and + the pre-existing order is guaranteed to remain unchanged. +

+ If is greater than the number of sources + in the list, the source is placed at the end of the list with an index + value one larger than any existing source. +

+ Win32 MSI API: + MsiSourceListAddSourceEx +

+
+ + + Clears sources of all types: network, url, and media. + +

+ Win32 MSI API: + MsiSourceListClearAll +

+
+ + + Removes all network sources from the list. URL sources are not affected. + +

+ Win32 MSI API: + MsiSourceListClearAllEx +

+
+ + + Removes all URL sources from the list. Network sources are not affected. + +

+ Win32 MSI API: + MsiSourceListClearAllEx +

+
+ + + Checks if the specified source exists in the list. + + case-insensitive source to look for + true if the source exists in the list, false otherwise + + + + Copies the network and URL sources from this list into an array. + + destination array to be filed + offset into the destination array where copying begins + + + + Removes a network or URL source. + +

+ Win32 MSI API: + MsiSourceListClearSource +

+
+ + + Enumerates the network and URL sources in the source list of the patch or product installation. + +

+ Win32 MSI API: + MsiSourceListEnumSources +

+
+ + + Forces the installer to search the source list for a valid + source the next time a source is required. For example, when the + installer performs an installation or reinstallation, or when it + requires the path for a component that is set to run from source. + +

+ Win32 MSI APIs: + MsiSourceListForceResolution, + MsiSourceListForceResolutionEx +

+
+ + + Gets the list of disks registered for the media source of + the patch or product installation. + + + + + Gets the number of network and URL sources in the list. + + + + + Gets a boolean value indicating whether the list is read-only. + A SourceList is never read-only. + + read-only status of the list + + + + Gets or sets the path relative to the root of the installation media. + + + + + Gets or sets the prompt template that is used when prompting the user + for installation media. + + + + + Gets or sets the most recently used source location for the product. + + + + + Gets or sets the name of the Windows Installer package or patch package + on the source. + + + + + Gets the type of the last-used source. + +

+

    +
  • "n" = network location
  • +
  • "u" = URL location
  • +
  • "m" = media location
  • +
  • (empty string) = no last used source
  • +
+

+
+ + + Gets or sets source list information properties of a product or patch installation. + + The source list information property name. + An unknown product, patch, or property was requested +

+ Win32 MSI API: + MsiSourceListGetInfo +

+
+ + + A list of source media for an installed product or patch. + + + + + Adds or updates a disk of the media source for the product or patch. + +

+ To change the disk prompt only, get the existing volume label from the + registry and provide it in this call along with the new disk prompt. + Passing a null or empty string for + registers an empty string as the volume label. +

To change the volume label only, get the existing disk prompt + that is registered and provide it when calling SourceListAddMediaDisk + along with the new volume label. Passing null or an empty string + registers an empty string as the disk prompt.

+

+ Win32 MSI API: + MsiSourceListAddMediaDisk +

+
+ + + Removes all source media from the list. + +

+ Win32 MSI API: + MsiSourceListClearAllEx +

+
+ + + Checks if the specified media disk id exists in the list. + + disk id of the media to look for + true if the media exists in the list, false otherwise + + + + Copies the source media info from this list into an array. + + destination array to be filed + offset into the destination array where copying begins + + + + Removes a specified disk from the set of registered disks. + + ID of the disk to remove +

+ Win32 MSI API: + MsiSourceListClearMediaDisk +

+
+ + + Enumerates the source media in the source list of the patch or product installation. + +

+ Win32 MSI API: + MsiSourceListEnumMediaDisks +

+
+ + + Gets the number of source media in the list. + + + + + Gets a boolean value indicating whether the list is read-only. + A SourceMediaList is never read-only. + + read-only status of the list + + + + Provides access to summary information of a Windows Installer database. + + + + + Gets a SummaryInfo object that can be used to examine, update, and add + properties to the summary information stream of a package or transform. + + Path to the package (database) or transform + True to reserve resources for writing summary information properties. + the package does not exist or could not be read + the package is an invalid format +

+ The SummaryInfo object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiGetSummaryInformation +

+
+ + + Formats and writes the previously stored properties into the standard summary information stream. + + The stream cannot be successfully written. +

+ This method may only be called once after all the property values have been set. Properties may + still be read after the stream is written. +

+ Win32 MSI API: + MsiSummaryInfoPersist +

+
+ + Gets or sets the Title summary information property. +

+ The Title summary information property briefly describes the type of installer package. Phrases + such as "Installation Database" or "Transform" or "Patch" may be used for this property. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Subject summary information property. +

+ The Subject summary information property conveys to a file browser the product that can be installed using + the logic and data in this installer database. For example, the value of the summary property for + Microsoft Office 97 would be "Microsoft Office 97 Professional". This value is typically set from the + installer property ProductName. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Author summary information property. +

+ The Author summary information property conveys to a file browser the manufacturer of the installation + database. This value is typically set from the installer property Manufacturer. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Keywords summary information property. +

+ The Keywords summary information property is used by file browsers to hold keywords that permit the + database file to be found in a keyword search. The set of keywords typically includes "Installer" as + well as product-specific keywords, and may be localized. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Comments summary information property. +

+ The Comments summary information property conveys the general purpose of the installer database. By convention, + the value for this summary property is set to the following: +

+ "This installer database contains the logic and data required to install <product name>." +

+ where <product name> is the name of the product being installed. In general the value for this summary + property only changes in the product name, nothing else. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Template summary information property. +

+ The Template summary information propery indicates the platform and language versions supported by the database. +

+ The syntax of the Template Summary property information is: + [platform property][,platform property][,...];[language id][,language id][,...] +

+ For example, the following are all valid values for the Template Summary property: + + Intel;1033 + Intel64;1033 + ;1033 + ; + Intel ;1033,2046 + Intel64;1033,2046 + Intel;0 + +

+ If this is a 64-bit Windows Installer, enter Intel64 in the Template summary information property. Note that an + installation package cannot have both the Intel and Intel64 properties set. +

+ If the current platform does not match one of the platforms specified then the installer will not process the + package. Not specifying a platform implies that the package is platform-independent. +

+ Entering 0 in the language ID field of the Template summary information property, or leaving this field empty, + indicates that the package is language neutral. +

+ There are variations of this property depending on whether it is in a source installer database or a transform. +

+ Source Installer Database - Only one language can be specified in a source installer database. Merge Modules are + the only packages that may have multiple languages. For more information, see Multiple Language Merge Modules. +

+ Transform - In a transform file, only one language may be specified. The specified platform and language determine + whether a transform can be applied to a particular database. The platform property and the language property can + be left blank if no transform restriction relies on them to validate the transform. +

+ This summary property is REQUIRED. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the LastSavedBy summary information property. +

+ The installer sets the Last Saved By summary information property to the value of the LogonUser property during + an administrative installation. The installer never uses this property and a user never needs to modify it. + Developers of a database editing tool may use this property to track the last person to modify the database. + This property should be left set to null in a final shipping database. +

+ In a transform, this summary property contains the platform and language ID(s) that a database should have + after it has been transformed. The property specifies to what the Template should be set in the new database. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the RevisionNumber summary information property. +

+ The Revision Number summary information property contains the package code for the installer package. The + package code is a unique identifier of the installer package. +

+ The Revision Number summary information property of a patch package specifies the GUID patch code for + the patch. This is followed by a list of patch code GUIDs for obsolete patches that are removed when this + patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list. +

+ The Revision Number summary information property of a transform package lists the product code GUIDs + and version of the new and original products and the upgrade code GUID. The list is separated with + semicolons as follows. +

+ Original-Product-Code Original-Product-Version ; New-Product Code New-Product-Version; Upgrade-Code +

+ This summary property is REQUIRED. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the CreatingApp summary information property. +

+ The CreatingApp summary information property conveys which application created the installer database. + In general the value for this summary property is the name of the software used to author this database. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the LastPrintTime summary information property. +

+ The LastPrintTime summary information property can be set to the date and time during an administrative + installation to record when the administrative image was created. For non-administrative installations + this property is the same as the CreateTime summary information property. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the CreateTime summary information property. +

+ The CreateTime summary information property conveys when the installer database was created. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the LastSaveTime summary information property. +

+ The LastSaveTime summary information property conveys when the last time the installer database was + modified. Each time a user changes an installation the value for this summary property is updated to + the current system time/date at the time the installer database was saved. Initially the value for + this summary property is set to null to indicate that no changes have yet been made. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the CodePage summary information property. +

+ The Codepage summary information property is the numeric value of the ANSI code page used for any + strings that are stored in the summary information. Note that this is not the same code page for + strings in the installation database. The Codepage summary information property is used to translate + the strings in the summary information into Unicode when calling the Unicode API functions. The + Codepage summary information property must be set before any string properties are set in the + summary information. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the PageCount summary information property. +

+ For an installation package, the PageCount summary information property contains the minimum + installer version required. For Windows Installer version 1.0, this property must be set to the + integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200. +

+ For a transform package, the PageCount summary information property contains minimum installer + version required to process the transform. Set to the greater of the two PageCount summary information + property values belonging to the databases used to generate the transform. +

+ The PageCount summary information property is set to null in patch packages. +

+ This summary property is REQUIRED. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the WordCount summary information property. +

+ The WordCount summary information property indicates the type of source file image. If this property is + not present, it defaults to 0. Note that this property is stored in place of the standard Count property. +

+ This property is a bit field. New bits may be added in the future. At present the following bits are + available: + + Bit 0: 0 = long file names, 1 = short file names + Bit 1: 0 = source is uncompressed, 1 = source is compressed + Bit 2: 0 = source is original media, 1 = source is administrative installation + [MSI 4.0] Bit 3: 0 = elevated privileges can be required to install, 1 = elevated privileges are not required to install + +

+ These are combined to give the WordCount summary information property one of the following values + indicating a type of source file image: + + 0 - Original source using long file names. Matches tree in Directory table. + 1 - Original source using short file names. Matches tree in Directory table. + 2 - Compressed source files using long file names. Matches cabinets and files in the Media table. + 3 - Compressed source files using short file names. Matches cabinets and files in the Media table. + 4 - Administrative image using long file names. Matches tree in Directory table. + 5 - Administrative image using short file names. Matches tree in Directory table. + +

+ Note that if the package is marked as compressed (bit 1 is set), the installer only installs files + located at the root of the source. In this case, even files marked as uncompressed in the File table must + be located at the root to be installed. To specify a source image that has both a cabinet file (compressed + files) and uncompressed files that match the tree in the Directory table, mark the package as uncompressed + by leaving bit 1 unset (value=0) in the WordCount summary information property and set + (value=16384) in the Attributes column of the File table + for each file in the cabinet. +

+ For a patch package, the WordCount summary information property specifies the patch engine that was used + to create the patch files. The default value is 1 and indicates that MSPATCH was used to create the patch + A value of "2" means that the patch is using smaller, optimized, files available only with Windows Installer + version 1.2 or later. A patch with a WordCount of "2" fails immediately if used with a Windows Installer + version earlier than 1.2. A patch with a WordCount of "3" fails immediately if used with a Windows Installer + version earlier than 2.0. +

+ This summary property is REQUIRED. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the CharacterCount summary information property. +

+ The CharacterCount summary information property is only used in transforms. This part of the summary + information stream is divided into two 16-bit words. The upper word contains the transform validation + flags. The lower word contains the transform error condition flags. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + Gets or sets the Security summary information property. +

+ The Security summary information property conveys whether the package should be opened as read-only. The database + editing tool should not modify a read-only enforced database and should issue a warning at attempts to modify a + read-only recommended database. The following values of this property are applicable to Windows Installer files: + + 0 - no restriction + 2 - read only recommended + 4 - read only enforced + +

+ This property should be set to read-only recommended (2) for an installation database and to read-only + enforced (4) for a transform or patch. +

+ Win32 MSI APIs: + MsiSummaryInfoGetProperty, + MsiSummaryInfoSetProperty +

+
+ + + Contains information about all the tables in a Windows Installer database. + + + + + Adds a new table to the database. + + information about the table to be added + a table with the same name already exists in the database + + + + Removes all tables (and all data) from the database. + + + + + Checks if the database contains a table with the given name. + + case-sensitive name of the table to search for + True if the table exists, false otherwise. + + + + Copies the table information from this collection into an array. + + destination array to be filed + offset into the destination array where copying begins + + + + Removes a table from the database. + + case-sensitive name of the table to be removed + true if the table was removed, false if the table did not exist + + + + Enumerates the tables in the database. + + + + + Gets the number of tables in the database. + + + + + Gets a boolean value indicating whether the collection is read-only. + A TableCollection is read-only when the database is read-only. + + read-only status of the collection + + + + Gets information about a given table. + + case-sensitive name of the table + information about the requested table, or null if the table does not exist in the database + + + + Defines a table in an installation database. + + + + + Creates a table definition. + + Name of the table. + Columns in the table. + The primary keys of the table. + + + + Gets a string representation of the table. + + The name of the table. + + + + Gets the name of the table. + + + + + Gets information about the columns in this table. + +

+ This property queries the database every time it is called, + to ensure the returned values are up-to-date. For best performance, + hold onto the returned collection if using it more than once. +

+
+ + + Gets the names of the columns that are primary keys of the table. + + + + + Gets an SQL CREATE string that can be used to create the table. + + + + + Gets an SQL INSERT string that can be used insert a new record into the table. + +

+ The values are expressed as question-mark tokens, to be supplied by the record. +

+
+ + + Gets an SQL SELECT string that can be used to select all columns of the table. + +

+ The columns are listed explicitly in the SELECT string, as opposed to using "SELECT *". +

+
+ + + Contains specific information about an error encountered by the , + , or methods of the + class. + + + + + Gets the type of validation error encountered. + + + + + Gets the column containing the error, or null if the error applies to the whole row. + + + + + A View represents a result set obtained when processing a query using the + method of a + . Before any data can be transferred, + the query must be executed using the method, passing to + it all replaceable parameters designated within the SQL query string. + + + + + Executes a SQL View query and supplies any required parameters. The query uses the + question mark token to represent parameters as described in SQL Syntax. The values of + these parameters are passed in as the corresponding fields of a parameter record. + + Optional Record that supplies the parameters. This + Record contains values to replace the parameter tokens in the SQL query. + the View could not be executed + the View handle is invalid +

+ Win32 MSI API: + MsiViewExecute +

+
+ + + Executes a SQL View query. + + the View could not be executed + the View handle is invalid +

+ Win32 MSI API: + MsiViewExecute +

+
+ + + Fetches the next sequential record from the view, or null if there are no more records. + + the View is not in an active state + the View handle is invalid +

+ The Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. +

+ Win32 MSI API: + MsiViewFetch +

+
+ + + Updates a fetched Record. + + specifies the modify mode + the Record to modify + the modification failed, + or a validation was requested and the data did not pass + the View handle is invalid +

+ You can update or delete a record immediately after inserting, or seeking provided you + have NOT modified the 0th field of the inserted or sought record. +

+ To execute any SQL statement, a View must be created. However, a View that does not + create a result set, such as CREATE TABLE, or INSERT INTO, cannot be used with any of + the Modify methods to update tables though the view. +

+ You cannot fetch a record containing binary data from one database and then use + that record to insert the data into another database. To move binary data from one database + to another, you should export the data to a file and then import it into the new database + using a query and the . This ensures that each database has + its own copy of the binary data. +

+ Note that custom actions can only add, modify, or remove temporary rows, columns, + or tables from a database. Custom actions cannot modify persistent data in a database, + such as data that is a part of the database stored on disk. +

+ Win32 MSI API: + MsiViewModify +

+ + + + + + + + + + + + + +
+ + + Refreshes the data in a Record. + + the Record to be refreshed + the refresh failed + the View handle is invalid +

+ The Record must have been obtained by calling . Fails with + a deleted Record. Works only with read-write Records. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Inserts a Record into the view. + + the Record to be inserted + the insertion failed + the View handle is invalid +

+ Fails if a row with the same primary keys exists. Fails with a read-only database. + This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Updates the View with new data from the Record. + + the new data + the update failed + the View handle is invalid +

+ Only non-primary keys can be updated. The Record must have been obtained by calling + . Fails with a deleted Record. Works only with read-write Records. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Updates or inserts a Record into the View. + + the Record to be assigned + the assignment failed + the View handle is invalid +

+ Updates record if the primary keys match an existing row and inserts if they do not match. + Fails with a read-only database. This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Updates or deletes and inserts a Record into the View. + + the Record to be replaced + the replacement failed + the View handle is invalid +

+ The Record must have been obtained by calling . Updates record if the + primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. + Fails with a read-only database. This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Deletes a Record from the View. + + the Record to be deleted + the deletion failed + the View handle is invalid +

+ The Record must have been obtained by calling . Fails if the row has been + deleted. Works only with read-write records. This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Inserts a Record into the View. The inserted data is not persistent. + + the Record to be inserted + the insertion failed + the View handle is invalid +

+ Fails if a row with the same primary key exists. Works only with read-write records. + This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Refreshes the information in the supplied record without changing the position + in the result set and without affecting subsequent fetch operations. + + the Record to be filled with the result of the seek + the seek failed + the View handle is invalid +

+ After seeking, the Record may then be used for subsequent Update, Delete, and Refresh + operations. All primary key columns of the table must be in the query and the Record must + have at least as many fields as the query. Seek cannot be used with multi-table queries. + This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Inserts or validates a record. + + the Record to be merged + true if the record was inserted or validated, false if there is an existing + record with the same primary keys that is not identical + the merge failed (for a reason other than invalid data) + the View handle is invalid +

+ Works only with read-write records. This method cannot be used with a + View containing joins. +

+ See for more remarks. +

+ Win32 MSI API: + MsiViewModify +

+
+ + + Validates a record, returning information about any errors. + + the Record to be validated + null if the record was validated; if there is an existing record with + the same primary keys that has conflicting data then error information is returned + the validation failed (for a reason other than invalid data) + the View handle is invalid +

+ The Record must have been obtained by calling . + Works with read-write and read-only records. This method cannot be used + with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI APIs: + MsiViewModify, + MsiViewGetError +

+
+ + + Validates a new record, returning information about any errors. + + the Record to be validated + null if the record was validated; if there is an existing + record with the same primary keys then error information is returned + the validation failed (for a reason other than invalid data) + the View handle is invalid +

+ Checks for duplicate keys. The Record must have been obtained by + calling . Works with read-write and read-only records. + This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI APIs: + MsiViewModify, + MsiViewGetError +

+
+ + + Validates fields of a fetched or new record, returning information about any errors. + Can validate one or more fields of an incomplete record. + + the Record to be validated + null if the record was validated; if there is an existing record with + the same primary keys that has conflicting data then error information is returned + the validation failed (for a reason other than invalid data) + the View handle is invalid +

+ Works with read-write and read-only records. This method cannot be used with + a View containing joins. +

+ See for more remarks. +

+ Win32 MSI APIs: + MsiViewModify, + MsiViewGetError +

+
+ + + Validates a record that will be deleted later, returning information about any errors. + + the Record to be validated + null if the record is safe to delete; if another row refers to + the primary keys of this row then error information is returned + the validation failed (for a reason other than invalid data) + the View handle is invalid +

+ Validation does not check for the existence of the primary keys of this row in properties + or strings. Does not check if a column is a foreign key to multiple tables. Works with + read-write and read-only records. This method cannot be used with a View containing joins. +

+ See for more remarks. +

+ Win32 MSI APIs: + MsiViewModify, + MsiViewGetError +

+
+ + + Enumerates over the Records retrieved by the View. + + An enumerator of Record objects. + The View was not d before attempting the enumeration. +

+ Each Record object should be d after use. + It is best that the handle be closed manually as soon as it is no longer + needed, as leaving lots of unused handles open can degrade performance. + However, note that it is not necessary to complete the enumeration just + for the purpose of closing handles, because Records are fetched lazily + on each step of the enumeration. +

+ Win32 MSI API: + MsiViewFetch +

+
+ + + Gets the Database on which this View was opened. + + + + + Gets the SQL query string used to open this View. + + + + + Gets the set of tables that were included in the SQL query for this View. + + + + + Gets the set of columns that were included in the query for this View, + or null if this view is not a SELECT query. + + the View is not in an active state + the View handle is invalid +

+ Win32 MSI API: + MsiViewGetColumnInfo +

+
+
+
diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_NET35.dll b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_NET35.dll new file mode 100644 index 0000000..09879e5 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_NET35.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_OS.dll b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_OS.dll new file mode 100644 index 0000000..00f337a Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_OS.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_WI45.dll b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_WI45.dll new file mode 100644 index 0000000..cbc01b7 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Calibry/SEQ_WI45.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/Setup.exe b/Calibry/5.0/PS_Deployment/Files/Calibry/Setup.exe new file mode 100644 index 0000000..363956c Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Calibry/Setup.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Calibry/configuration.ini b/Calibry/5.0/PS_Deployment/Files/Calibry/configuration.ini new file mode 100644 index 0000000..18356d0 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Calibry/configuration.ini @@ -0,0 +1,119 @@ +[CPPBootstrapper_Products] +Product1 = Product_WI +Product2 = Product_NET + +[EnternalUI] +Path = Calibry\InstallationUI.exe + +[OS] +Path = Calibry\SEQ_OS.dll + +[Product_WI] +ID = 1 +Name=Windows Installer 4.5 +Description= Windows Installer 4.5 +LibPath = Calibry\SEQ_WI45.dll +logfilePath = \WindowsUpdate.log +ALL_OS_BS = +VISTA_X86 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +VISTA_X86_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +VISTA_X86_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +VISTA_X64 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +VISTA_X64_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +VISTA_X64_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +VISTA_IA64 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +VISTA_IA64_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +VISTA_IA64_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +WINXP_X86_SP2 = 3rdParty\WI45\WindowsXP-KB942288-v3-x86.exe +WINXP_X86_SP3 = 3rdParty\WI45\WindowsXP-KB942288-v3-x86.exe +WINXP_X64_SP2 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x64.exe +WINXP_X64_SP3 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x64.exe +WIN2003_X86_SP1 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x86.exe +WIN2003_X86_SP2 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x86.exe +WIN2003_X64_SP1 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x64.exe +WIN2003_X64_SP2 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-x64.exe +WIN2003_IA64_SP1 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-ia64.exe +WIN2003_IA64_SP2 = 3rdParty\WI45\WindowsServer2003-KB942288-v4-ia64.exe +WIN2008_X86 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +WIN2008_X86_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +WIN2008_X86_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-x86.msu +WIN2008_X64 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +WIN2008_X64_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +WIN2008_X64_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-x64.msu +WIN2008_IA64 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +WIN2008_IA64_SP1 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +WIN2008_IA64_SP2 = 3rdParty\WI45\Windows6.0-KB942288-v2-ia64.msu +WIN7_X86 = na +WIN7_X64 = na +WIN2008R2_X64 = na +CmdLine = /norestart /quiet +StatusEntryPoint = IsPackageInstalled +InstallEntryPoint = InstallPackage +MetaInfoEntryPoint = SetMetaInfo +ShowParameterEntryPoint = ShowParameterUI +FilterEntryPoint = IsSequoiaPrerequisite +LogFileEntryPoint = SetLogFileHandle +ForcedRestart = 2 +;0=none 1=immediate 2=postponed 3=Immediate_result_dependent 4 = Postponed_result_dependent +Optional = 0 +;Product installation optional status if this set to false check box for the corresponding product will not be displayed + +[Product_NET] +ID=2 +Name=Microsoft .NET 3.5 +Description= .NET 3.5 +LibPath = Calibry\SEQ_NET35.dll +logfilePath = \dd_dotnetfx35install.log +ALL_OS_BS = 3rdParty\NET35\dotnetfx35_SP1.exe +CmdLine = /q /norestart +StatusEntryPoint = IsPackageInstalled +InstallEntryPoint = InstallPackage +MetaInfoEntryPoint = SetMetaInfo +ShowParameterEntryPoint = ShowParameterUI +FilterEntryPoint = IsSequoiaPrerequisite +LogFileEntryPoint = SetLogFileHandle +ForcedRestart = 2 +Optional = 0 + +[Product_KB981145] +ID=3 +Name=Microsoft KB981145 +Description= KB981145 +LibPath = Calibry\SEQ_KB.dll +logfilePath = \WindowsUpdate.log +ALL_OS_BS = +VISTA_X86 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +VISTA_X86_SP1 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +VISTA_X86_SP2 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +VISTA_X64 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +VISTA_X64_SP1 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +VISTA_X64_SP2 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +WINXP_X86_SP2 = 3rdParty\MSKB\NDP20SP2-KB981145-x86.exe +WINXP_X86_SP3 = 3rdParty\MSKB\NDP20SP2-KB981145-x86.exe +WINXP_X64_SP2 = 3rdParty\MSKB\NDP20SP2-KB981145-x64.exe +WINXP_X64_SP3 = 3rdParty\MSKB\NDP20SP2-KB981145-x64.exe +WIN2003_X86_SP1 = 3rdParty\MSKB\NDP20SP2-KB981145-x86.exe +WIN2003_X86_SP2 = 3rdParty\MSKB\NDP20SP2-KB981145-x86.exe +WIN2003_X64_SP1 = 3rdParty\MSKB\NDP20SP2-KB981145-x64.exe +WIN2003_X64_SP2 = 3rdParty\MSKB\NDP20SP2-KB981145-x64.exe +WIN2008_X86 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +WIN2008_X86_SP1 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +WIN2008_X86_SP2 = 3rdParty\MSKB\Windows6.0-KB981145-x86.msu +WIN2008_X64 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +WIN2008_X64_SP1 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +WIN2008_X64_SP2 = 3rdParty\MSKB\Windows6.0-KB981145-x64.msu +WIN7_X86 = 3rdParty\MSKB\Windows6.1-KB981145-v2-x86.msu +WIN7_X64 = 3rdParty\MSKB\Windows6.1-KB981145-v2-x64.msu +WIN2008R2_X64 = 3rdParty\MSKB\Windows6.1-KB981145-v2-x64.msu +CmdLine = /norestart /quiet +StatusEntryPoint = IsPackageInstalled +InstallEntryPoint = InstallPackage +MetaInfoEntryPoint = SetMetaInfo +ShowParameterEntryPoint = ShowParameterUI +FilterEntryPoint = IsSequoiaPrerequisite +LogFileEntryPoint = SetLogFileHandle +ForcedRestart = 1 +;0=none 1=immediate 2=postponed 3=Immediate_result_dependent 4 = Postponed_result_dependent +Optional = 0 +;Product installation optional status if this set to false check box for the corresponding product will not be displayed + diff --git a/Calibry/5.0/PS_Deployment/Files/Configuration.xml b/Calibry/5.0/PS_Deployment/Files/Configuration.xml new file mode 100644 index 0000000..b978e0e --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/Configuration.xml @@ -0,0 +1,162 @@ + + + + false + false + false + None + None + + + + <Subtitle /> + </AuditSettings> + <BalanceSettings> + <AutoResolution4Decimal>100</AutoResolution4Decimal> + <AutoResolution5Decimal>10</AutoResolution5Decimal> + <DefaultBalance /> + <WarningLimits>0.03</WarningLimits> + <OutOfOrderLimits>0.1</OutOfOrderLimits> + <McpSettings> + <ReferenceMeasurementTime>30</ReferenceMeasurementTime> + <VibrationAdapter>Unstable</VibrationAdapter> + <ProcessAdapter>Off</ProcessAdapter> + <Repeatability>VeryQuickRelease</Repeatability> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </McpSettings> + <XpSettings> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </XpSettings> + <AxSettings> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </AxSettings> + <SagSettings> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </SagSettings> + <AgSettings> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </AgSettings> + <GenericsSettings> + <RecallSettings> + <RecallForInternal>0</RecallForInternal> + <RecallForInternalExternal>0</RecallForInternalExternal> + <RecallForExternalInternalExternal>0</RecallForExternalInternalExternal> + </RecallSettings> + </GenericsSettings> + </BalanceSettings> + <CalibrationSettings> + <MaxUndoAllowed>10</MaxUndoAllowed> + <UnitType>Weight</UnitType> + <DisplayUnit>true</DisplayUnit> + <MinimumAsFoundMeasurements>2</MinimumAsFoundMeasurements> + <MinimumAsReturnedMeasurements>2</MinimumAsReturnedMeasurements> + <DefaultAsReturnedMeasurements>4</DefaultAsReturnedMeasurements> + <DefaultAsFoundMeasurement>4</DefaultAsFoundMeasurement> + <CalibrationUpDown>Ascending</CalibrationUpDown> + <TareBetweenReadings>false</TareBetweenReadings> + <CompensateEvaporation>false</CompensateEvaporation> + <LockCompensateEvaporation>false</LockCompensateEvaporation> + <IsOrderNumberRequired>false</IsOrderNumberRequired> + <OpenTime>4</OpenTime> + <ReadTime>7</ReadTime> + </CalibrationSettings> + <DatabaseSettings> + <SqlServerName>RAP-Calibary-DB.crii.org</SqlServerName> + <DatabaseName>CalibryBiomed</DatabaseName> + </DatabaseSettings> + <EnvironmentalsSettings> + <ExternalCaptureToolAddress /> + <ExternalCaptureToolPort>0</ExternalCaptureToolPort> + <UseTableOnly>false</UseTableOnly> + <UseOneTemperature>true</UseOneTemperature> + <IsHumidityFixed>false</IsHumidityFixed> + <IsWaterTemperatureFixed>false</IsWaterTemperatureFixed> + <IsPressureFixed>false</IsPressureFixed> + <UseExternalCaptureTool>false</UseExternalCaptureTool> + <MinAirTemperature>0</MinAirTemperature> + <MaxAirTemperature>0</MaxAirTemperature> + <MinWaterTemperature>0</MinWaterTemperature> + <MaxWaterTemperature>0</MaxWaterTemperature> + <MinPressure>0</MinPressure> + <MaxPressure>0</MaxPressure> + <MinHumidity>0</MinHumidity> + <MaxHumidity>0</MaxHumidity> + <MinZFactor>0</MinZFactor> + <MaxZFactor>0</MaxZFactor> + <PromptSecondRead>false</PromptSecondRead> + </EnvironmentalsSettings> + <ReportSettings> + <DefaultSummaryView>false</DefaultSummaryView> + <NoPromptForExportFileName>false</NoPromptForExportFileName> + <AutoShowReport>false</AutoShowReport> + <AutoPrintReport>false</AutoPrintReport> + <FontName>Arial</FontName> + <TitelFontSize>16</TitelFontSize> + <SubtitelFontSize>10</SubtitelFontSize> + <TextFontSize>6</TextFontSize> + <NoticeFontSize>8</NoticeFontSize> + <TopTitle>50</TopTitle> + <AlignTitel>MiddleCenter</AlignTitel> + <Top>100</Top> + <ShowHumidity>true</ShowHumidity> + <ShowInspectionStatus>true</ShowInspectionStatus> + <ShowMeanStatistics>true</ShowMeanStatistics> + <ShowAbsoluteSystematicErrorStatistics>true</ShowAbsoluteSystematicErrorStatistics> + <ShowAbsoluteRandomErrorStatistics>true</ShowAbsoluteRandomErrorStatistics> + <ShowRelativeSystematicErrorStatistics>true</ShowRelativeSystematicErrorStatistics> + <showRelativeRandomErrorStatistics>true</showRelativeRandomErrorStatistics> + <ShowUncertaintyStatistics>true</ShowUncertaintyStatistics> + <ShowNotice>true</ShowNotice> + <ShowNoticeTitle>true</ShowNoticeTitle> + <ShowUndos>true</ShowUndos> + <ShowNextDueDate>true</ShowNextDueDate> + <ShowGraph>true</ShowGraph> + <ShowTime>false</ShowTime> + <SignReport>false</SignReport> + <Logo1> + <PosX>100</PosX> + <PosY>100</PosY> + <Displays>PageHeader</Displays> + </Logo1> + <Logo2> + <PosX>100</PosX> + <PosY>100</PosY> + <Displays>PageHeader</Displays> + </Logo2> + <Logo3> + <PosX>100</PosX> + <PosY>100</PosY> + <Displays>PageHeader</Displays> + </Logo3> + </ReportSettings> + <SoundSettings> + <EnableNextVolume>false</EnableNextVolume> + <EnableError>false</EnableError> + <EnableFinish>false</EnableFinish> + <EnableOutOfTolerance>false</EnableOutOfTolerance> + </SoundSettings> + <RfidSettings> + <ShouldFilterLists>false</ShouldFilterLists> + <ShouldShowQuickAccess>true</ShouldShowQuickAccess> + </RfidSettings> +</clsUserSettings> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/Install.exe b/Calibry/5.0/PS_Deployment/Files/Install.exe new file mode 100644 index 0000000..0640545 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Install.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Patch for calibry.exe b/Calibry/5.0/PS_Deployment/Files/Patch for calibry.exe new file mode 100644 index 0000000..f25bebb Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Patch for calibry.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Actor Control.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Actor Control.x32 new file mode 100644 index 0000000..7843122 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Actor Control.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Animated GIF Asset.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Animated GIF Asset.x32 new file mode 100644 index 0000000..7f7a824 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Animated GIF Asset.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/BUDAPI.X32 b/Calibry/5.0/PS_Deployment/Files/Xtras/BUDAPI.X32 new file mode 100644 index 0000000..e188c4f Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/BUDAPI.X32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Buddy API Xtra.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Buddy API Xtra.x32 new file mode 100644 index 0000000..8310430 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Buddy API Xtra.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Dirapi.dll b/Calibry/5.0/PS_Deployment/Files/Xtras/Dirapi.dll new file mode 100644 index 0000000..96ab2dd Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Dirapi.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset Dialog.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset Dialog.x32 new file mode 100644 index 0000000..0022988 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset Dialog.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset.x32 new file mode 100644 index 0000000..3e80dda Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Asset.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Font Xtra.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Xtra.x32 new file mode 100644 index 0000000..e09450d Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Font Xtra.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/INetURL.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/INetURL.x32 new file mode 100644 index 0000000..2322f1c Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/INetURL.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Icon.ico b/Calibry/5.0/PS_Deployment/Files/Xtras/Icon.ico new file mode 100644 index 0000000..45e72fe Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Icon.ico differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Iml32.dll b/Calibry/5.0/PS_Deployment/Files/Xtras/Iml32.dll new file mode 100644 index 0000000..08d5aa9 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Iml32.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/MSVCRT.DLL b/Calibry/5.0/PS_Deployment/Files/Xtras/MSVCRT.DLL new file mode 100644 index 0000000..16bb340 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/MSVCRT.DLL differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/MacroMix.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/MacroMix.x32 new file mode 100644 index 0000000..c06c36a Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/MacroMix.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/NetFile.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/NetFile.x32 new file mode 100644 index 0000000..f22788c Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/NetFile.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/NetLingo.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/NetLingo.x32 new file mode 100644 index 0000000..88f49a6 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/NetLingo.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Proj.dll b/Calibry/5.0/PS_Deployment/Files/Xtras/Proj.dll new file mode 100644 index 0000000..76d218e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Proj.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/Text Asset.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/Text Asset.x32 new file mode 100644 index 0000000..dbdfaaf Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/Text Asset.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/TextAuth.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/TextAuth.x32 new file mode 100644 index 0000000..a29424b Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/TextAuth.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/Xtras/TextXtra.x32 b/Calibry/5.0/PS_Deployment/Files/Xtras/TextXtra.x32 new file mode 100644 index 0000000..dd9fc7e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/Xtras/TextXtra.x32 differ diff --git a/Calibry/5.0/PS_Deployment/Files/autorun.inf b/Calibry/5.0/PS_Deployment/Files/autorun.inf new file mode 100644 index 0000000..c39beb1 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/autorun.inf @@ -0,0 +1,3 @@ +[autorun] +open=install.exe +icon=Xtras\Icon.ico \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/0x0409.ini b/Calibry/5.0/PS_Deployment/Files/capture/0x0409.ini new file mode 100644 index 0000000..77745a6 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/0x0409.ini @@ -0,0 +1,111 @@ + +[0x0409] +TITLE=Choose Setup Language +DESCRIPTION=Select the language for this installation from the choices below. +REBOOTMESSAGE=The installer must restart your system to complete configuring the Windows Installer service. Click Yes to restart now or No if you plan to restart later. +ONUPGRADE=This setup will perform an upgrade of '%s'. Do you want to continue? +LATERVERSIONINSTALLED=A later version of '%s' is already installed on this machine. The setup cannot continue. +OK=OK +Cancel=Cancel +Password=Password: +Install=Install +1100=Setup Initialization Error +1101=%s +1102=%s Setup is preparing the %s, which will guide you through the program setup process. Please wait. +1103=Checking Operating System Version +1104=Checking Windows(R) Installer Version +1105=Configuring Windows Installer +1106=Configuring %s +1107=Setup has completed configuring the Windows Installer on your system. The system needs to be restarted in order to continue with the installation. Please click Restart to reboot the system. +1108=%s +1150=Setup has detected an incompatible version of Windows. Please click OK and verify that the target system is running either Windows 95 (or later version), or Windows NT 4.0 Service Pack 6 (or later version), before relaunching the installation +1151=Error writing to the temporary location +1152=Error extracting %s to the temporary location +1153=Error reading setup initialization file +1154=Installer not found in %s +1155=File %s not found +1156=Internal error in Windows Installer +1158=Error populating strings. Verify that all strings in Setup.ini are valid. +1200=Restart +1603=Error installing Windows Installer engine. A file which needs to be replaced may be held in use. Close all applications and try again. +1201=Setup needs %lu KB free disk space in %s. Please free up some space and try again +1202=You do not have sufficient privileges to complete this installation for all users of the machine. Log on as administrator and then retry this installation +1203=Command line parameters: +1204=/L language ID +1205=/S Hide intialization dialog. For silent mode use: /S /v/qn. +1206=/V parameters to MsiExec.exe +1207=Windows(R) Installer %s found. This is an older version of the Windows(R) Installer. Click OK to continue. +1208=ANSI code page for %s is not installed on the system and therefore setup cannot run in the selected language. Run the setup and select another language. +1210=Setup requires Windows Installer version %s or higher to install the Microsoft .NET Framework version 2.0. Please install the Windows Installer version %s or higher and try again. +1604=This setup does not contain the Windows Installer engine (%s) required to run the installation on this operating system. +1607=Unable to install %s Scripting Runtime. +1608=Unable to create InstallDriver instance, Return code: %d +1609=Please specify a location to save the installation package. +1611=Unable to extract the file %s. +1612=Extracting files. +1613=Downloading file %s. +1614=An error occurred while downloading the file %s. What would you like to do? +1615=hr +1616=min +1617=sec +1618=MB +1619=KB +1620=/sec +1621=Failed to verify signature of file %s. +1622=Estimated time remaining: +1623=%d KB of %d KB downloaded at +1624=Preparing to Install... +1625=Get help for this installation. +1626=Help +1627=Unable to save file: %s +1628=Failed to complete installation. +1629=Invalid command line. +1630=/UA<url to InstMsiA.exe> +1631=/UW<url to InstMsiW.exe> +1632=/UM<url to msi package> +1633=/US<url to IsScript.msi> +1634=Setup Initialization Error, failed to clone the process. +1635=The file %s already exists. Would you like to replace it? +1636=/P password mode +1637=/A administrative installation +1638=/J advertise mode +1639=/X uninstall mode +1640=/F repair mode +1641=/B cache installation locally +1642=Could not verify signature. You need Internet Explorer 3.02 or later with Authenticode update. +1643=Setup requires a newer version of WinInet.dll. You may need to install Internet Explorer 3.02 or later. +1644=You do not have sufficient privileges to complete this installation. Log on as administrator and then retry this installation +1645=Error installing Microsoft(R) .NET Framework, Return Code: %d +1646=%s optionally uses the Microsoft (R) .NET %s Framework. Would you like to install it now? +1648=Setup has detected an incompatible version of Windows. Please click OK and verify that the target system is running either Windows 95 (or later version), or Windows NT 4.0 Service Pack 3 (or later version), before relaunching the installation +1649=%s optionally uses the Visual J# Redistributable Package. Would you like to install it now? +1650= (This will also install the .NET Framework.) +1651=Setup has detected an incompatible version of Windows. Please click OK and verify that the target system is running Windows 2000 Service Pack 3 (or later version), before relaunching the installation + +1652=%s requires that the following requirements be installed on your computer prior to installing this application. Click Install to begin installing these requirements: +1653=Installing %s +1654=Would you like to cancel the setup after %s has finished installing? +1655=The files for installation requirement %s could not be found. The installation will now stop. This is probably due to a failed, or canceled download. +1656=The installation of %s appears to have failed. Do you want to continue the installation? +1657=Succeeded +1658=Installing +1659=Pending +1660=Installed +1661=Status +1662=Requirement +1663=Failed +1664=Extracting +1665=Downloading +1666=Skipped +1667=The installation of %s has failed. Setup will now exit. +1668=The installation of %s requires a reboot. Click Yes to restart now or No if you plan to restart later. +1669=%1 optionally uses %2. Would you like to install it now? + +1670=Unable to load module %s, Error Code: %d +1671=Downloading file %2 of %3: %1 +1700=An error occurred initializing the InstallScript engine +1701=Unable to extract InstallScript engine support files to temp location + +[Languages] +0x0404=Chinese (Traditional) +0x0804=Chinese (Simplified) diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Autorun.inf b/Calibry/5.0/PS_Deployment/Files/capture/Autorun.inf new file mode 100644 index 0000000..618e64c --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Autorun.inf @@ -0,0 +1,2 @@ +[autorun] +OPEN=setup.exe diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Calibry capture tool.msi b/Calibry/5.0/PS_Deployment/Files/capture/Calibry capture tool.msi new file mode 100644 index 0000000..3403847 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/Calibry capture tool.msi differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Setup.ini b/Calibry/5.0/PS_Deployment/Files/capture/Setup.ini new file mode 100644 index 0000000..c2183ff --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Setup.ini @@ -0,0 +1,182 @@ +[Info] +Name=INTL +Version=1.00.000 +DiskSpace=8000 ;DiskSpace requirement in KB + +[Startup] +CmdLine= +SuppressWrongOS=Y +ScriptDriven=0 +ScriptVer=1.0.0.1 +DotNetOptionalInstallIfSilent=N +OnUpgrade=1 +RequireExactLangMatch=0404,0804 +Product=Calibry capture tool +PackageName=Calibry capture tool.msi +EnableLangDlg=N +LogResults=N +DoMaintenance=N +ProductCode={1E246158-8BA8-4729-AA70-FB0CD1002D41} +ProductVersion=1.00.0000 +SuppressReboot=Y +LauncherName=setup.exe +AdvertiseWhileElevated=Y +DotNetVersion=v2.0.50727 +DotNetSPRequired= +UseDotNetUI=N +DotNetCoreSetupUILang=1033 +DotNetLangPacks= +DotNetFxCmd= +DotNetLangPackCmd= +DotNetOptional=Y +DotNetDelayReboot=Y +PackageCode={7110E558-4E83-4C5C-AD2E-D81DE89A7A95} + +[MsiVersion] +3.1.4000.2435=SupportOSMsi30 + +[SupportOSMsi11] ;Supported platforms for MSI 1.1 +Win95=1 +Win98=1 +WinNT4SP3=1 + +[SupportOSMsi12] ;Supported platforms for MSI 1.2 +Win95=1 +Win98=1 +WinME=1 +WinNT4SP3=1 + +[SupportOS] ;Supported platforms for MSI 2.0 +Win95=1 +Win98=1 +WinME=1 +WinNT4SP6=1 +Win2K=1 + +[SupportOSMsi30] ;Supported platforms for MSI 3.0 +Win2KSP3=1 +WinXP=1 +Win2003Server=1 + +[Win95] +MajorVer=4 +MinorVer=0 +MinorVerMax=1 +BuildNo=950 +PlatformId=1 + +[Win98] +MajorVer=4 +MinorVer=10 +MinorVerMax=11 +BuildNo=1998 +PlatformId=1 + +[WinME] +MajorVer=4 +MinorVer=90 +MinorVerMax=91 +BuildNo=3000 +PlatformId=1 + +[WinNT4SP3] +MajorVer=4 +MinorVer=0 +MinorVerMax=1 +BuildNo=1381 +PlatformId=2 +ServicePack=768 + +[WinNT4SP6] +MajorVer=4 +MinorVer=0 +MinorVerMax=1 +BuildNo=1381 +PlatformId=2 +ServicePack=1536 + +[Win2K] +MajorVer=5 +MinorVer=0 +MinorVerMax=1 +BuildNo=2195 +PlatformId=2 + +[Win2KSP3] +MajorVer=5 +MinorVer=0 +MinorVerMax=1 +BuildNo=2195 +PlatformId=2 +ServicePack=768 + +[WinXP] +MajorVer=5 +MinorVer=1 +MinorVerMax=2 +BuildNo=2600 +PlatformId=2 + +[Win2003Server] +MajorVer=5 +MinorVer=2 +MinorVerMax=3 +BuildNo=2600 +PlatformId=2 + +[0x0409] +TITLE=Choose Setup Language +DESCRIPTION=Select the language for this installation from the choices below. +OK=OK +Cancel=Cancel +0x0409=English (United States) +0x0411=Japanese +0x0401=Arabic (Saudi Arabia) +0x042d=Basque +0x0402=Bulgarian +0x0403=Catalan +0x0804=Chinese (PRC) +0x0404=Chinese (Taiwan) +0x041a=Croatian +0x0405=Czech +0x0406=Danish +0x0413=Dutch (Netherlands) +0x040b=Finnish +0x0c0c=French (Canada) +0x040c=French (France) +0x0407=German (Germany) +0x0408=Greek +0x040d=Hebrew +0x040e=Hungarian +0x0421=Indonesian +0x0410=Italian (Italy) +0x0412=Korean +0x0414=Norwegian (Bokmal) +0x0415=Polish +0x0416=Portuguese (Brazil) +0x0816=Portuguese (Portugal) +0x0418=Romanian +0x0419=Russian +0x0c1a=Serbian (Cyrillic) +0x041b=Slovak +0x0424=Slovenian +0x040a=Spanish (Traditional Sort) +0x041d=Swedish +0x041e=Thai +0x041f=Turkish + +[Languages] +count=1 +default=409 +key0=409 +[Calibry capture tool.msi] +Type=0 +Location=Calibry capture tool.msi +[Setup.bmp] +Type=0 +[dotnetfx.exe] +Type=0 +Location=isnetfx.exe +[WindowsInstaller-KB893803-x86.exe] +Type=0 +Location=WindowsInstaller-KB893803-x86.exe diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Communication.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Communication.cs new file mode 100644 index 0000000..f1ed08c --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Communication.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Sockets; + +namespace TestoCaptureTool +{ + /// <summary> + /// State object for reading client data asynchronously + /// </summary> + public class StateObject + { + public Socket workSocket; // Client socket. + public const int BufferSize = 1024; // Size of receive buffer. + public byte[] buffer = new byte[BufferSize]; // Receive buffer. + public StringBuilder sb = new StringBuilder(); // Received data string. + } + + /// <summary> + /// Class which listen to incoming request from calibry and reply to the query + /// </summary> + class Communication + { + Socket listenSocket; + bool _listening; + bool _closing = false; + + public static System.Threading.ManualResetEvent allDone = new System.Threading.ManualResetEvent(false); + + public void StopListening() + { + _closing = true; + allDone.Set(); + listenSocket.Close(); + _listening = false; + } + + /// <summary> + /// Function which open the connection and start listening for incoming + /// requests. + /// </summary> + public void OpenCommunication() + { + try + { + // create the socket + listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + // bind the listening socket to the port (specified in the settings.settings file + IPEndPoint ep = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.NetworkPort); + listenSocket.Bind(ep); + } + catch (SocketException e) + { + listenSocket.Close(); + System.Diagnostics.Trace.TraceError(String.Format("{0}: {1}", e.ErrorCode, e.Message)); + return; + } + // start listening + + _listening = true; + } + + public void StartListening() + { + listenSocket.Listen(100); + + //Listen to incoming request until the application ends. + while (!_closing) + { + try + { + // Set the event to nonsignaled state. + allDone.Reset(); + + // Start an asynchronous socket to listen for connections. + listenSocket.BeginAccept(new AsyncCallback(AcceptCallback), listenSocket); + + // Wait until a connection is made before continuing. + allDone.WaitOne(); + + } + catch (System.Threading.ThreadInterruptedException) + { + _closing=true; + } + } + } + + public bool IsListening + { + get { return _listening; } + } + + /// <summary> + /// Function which handle an incoming request. + /// </summary> + /// <param name="ar"></param> + private static void AcceptCallback(IAsyncResult ar) + { + // Signal the main thread to continue. + allDone.Set(); + + // Get the socket that handles the client request. + Socket listener = (Socket)ar.AsyncState; + + try + { + Socket handler = listener.EndAccept(ar); + + // Create the state object. + StateObject state = new StateObject(); + state.workSocket = handler; + handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); + } + catch (ObjectDisposedException) + { + System.Diagnostics.Trace.TraceInformation("Socket closed"); + } + } + + /// <summary> + /// Function which interpret the receiving string + /// </summary> + /// <param name="ar"></param> + private static void ReadCallback(IAsyncResult ar) + { + // Retrieve the state object and the handler socket + // from the asynchronous state object. + StateObject state = (StateObject)ar.AsyncState; + Socket handler = state.workSocket; + int bytesRead = handler.EndReceive(ar);; + + + // There might be more data, so store the data received so far. + state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead)); + string receivedString = state.sb.ToString(); + + //If format is not Calibry format, closing the communication + if (!receivedString.StartsWith("<bal m=\"r\">")) + { + handler.Shutdown(SocketShutdown.Both); + handler.Close(); + return; + } + + //Retrieving the different value for the different parameter received + //from calibry. Could be used to request the data from a specific device + //depending on these parameters + int dvnStart=receivedString.IndexOf("<dvn>"); + int dsnStart = receivedString.IndexOf("<dsn>"); + int locStart = receivedString.IndexOf("<loc>"); + + string deviceNumber = receivedString.Substring(dvnStart + 5, dsnStart - (dvnStart + 5)); + string serialNumber = receivedString.Substring(dsnStart + 5, locStart - (dsnStart + 5)); + string location = receivedString.Substring(locStart+5); + + //Proceeding with the reply + Send(handler, deviceNumber, serialNumber, location); + } + + /// <summary> + /// Building the answer to the request + /// Sending the answer + /// Terminating the communication + /// </summary> + /// <param name="handler"></param> + /// <param name="deviceNumber"></param> + /// <param name="serialNumber"></param> + /// <param name="location"></param> + private static void Send(Socket handler, string deviceNumber, string serialNumber, string location) + { + //Sending only the parameter for which a channel is configured. + StringBuilder sentDate = new StringBuilder(); + sentDate.Append("<bal m=\"r\"><dvn>"); + sentDate.Append(deviceNumber); + + if (Program._device.IsConnected) + { + if (Program._device.AirTemperatureChannel > -1) + { + sentDate.Append("<tea>"); + sentDate.Append(Program._device.AirTemperature.ToString("0.00",System.Globalization.CultureInfo.CurrentUICulture)); + sentDate.Append(";"); + sentDate.Append(Program._device.DeviceSN); + } + + if (Program._device.WaterTemperatureChannel > -1) + { + sentDate.Append("<tew>"); + sentDate.Append(Program._device.WaterTemperature.ToString("0.00",System.Globalization.CultureInfo.CurrentUICulture)); + sentDate.Append(";"); + sentDate.Append(Program._device.DeviceSN); + } + + if (Program._device.HumidityChannel > -1) + { + sentDate.Append("<hgr>"); + sentDate.Append(Program._device.Humidity.ToString("0.00",System.Globalization.CultureInfo.CurrentUICulture)); + sentDate.Append(";"); + sentDate.Append(Program._device.DeviceSN); + } + + if (Program._device.PressureChannel > -1) + { + sentDate.Append("<prs>"); + sentDate.Append(Program._device.Pressure.ToString("0.00",System.Globalization.CultureInfo.CurrentUICulture)); + sentDate.Append(";"); + sentDate.Append(Program._device.DeviceSN); + } + } + + // Convert the string data to byte data using ASCII encoding. + byte[] byteData = Encoding.ASCII.GetBytes(sentDate.ToString()); + + // Begin sending the data to the remote device. + handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler); + } + + /// <summary> + /// When all data have been send, terminating the communication + /// </summary> + /// <param name="ar"></param> + private static void SendCallback(IAsyncResult ar) + { + try + { + // Retrieve the socket from the state object. + Socket handler = (Socket)ar.AsyncState; + + // Complete sending the data to the remote device. + handler.EndSend(ar); + + handler.Shutdown(SocketShutdown.Both); + handler.Close(); + + } + catch (Exception e) + { + System.Diagnostics.Trace.WriteLine(e.ToString()); + } + } + + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.Designer.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.Designer.cs new file mode 100644 index 0000000..78a7a95 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.Designer.cs @@ -0,0 +1,458 @@ +namespace TestoCaptureTool +{ + partial class ConfigurationForm + { + /// <summary> + /// Required designer variable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Clean up any resources being used. + /// </summary> + /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// <summary> + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// </summary> + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigurationForm)); + this.lblAirTemperature = new System.Windows.Forms.Label(); + this.lblWaterTemperature = new System.Windows.Forms.Label(); + this.lblHumidity = new System.Windows.Forms.Label(); + this.lblPressure = new System.Windows.Forms.Label(); + this.txtAirTemperature = new System.Windows.Forms.TextBox(); + this.txtPressure = new System.Windows.Forms.TextBox(); + this.txtHumidity = new System.Windows.Forms.TextBox(); + this.txtWaterTemperature = new System.Windows.Forms.TextBox(); + this.lblDeviceSN = new System.Windows.Forms.Label(); + this.lblCaptureTime = new System.Windows.Forms.Label(); + this.txtDeviceSN = new System.Windows.Forms.TextBox(); + this.txtCaptureTime = new System.Windows.Forms.TextBox(); + this.lblDeviceModel = new System.Windows.Forms.Label(); + this.txtDeviceModel = new System.Windows.Forms.TextBox(); + this.lblConnectionStatus = new System.Windows.Forms.Label(); + this.txtConnectionStatus = new System.Windows.Forms.TextBox(); + this.grbCapture = new System.Windows.Forms.GroupBox(); + this.lblAutoReadingNameHeader = new System.Windows.Forms.Label(); + this.lblAutoReadingPortHeader = new System.Windows.Forms.Label(); + this.lblCalibrySetup = new System.Windows.Forms.Label(); + this.lblAutoReadingHeader = new System.Windows.Forms.Label(); + this.lblAutoReadingValue = new System.Windows.Forms.Label(); + this.lblAutoReadingNameValue = new System.Windows.Forms.Label(); + this.lblAutoReadingPortValue = new System.Windows.Forms.Label(); + this.grbConfigureCalibry = new System.Windows.Forms.GroupBox(); + this.cmbDeviceModel = new System.Windows.Forms.ComboBox(); + this.lblDevice = new System.Windows.Forms.Label(); + this.btnConnect = new System.Windows.Forms.Button(); + this.lblComPort = new System.Windows.Forms.Label(); + this.cmbCOMPort = new System.Windows.Forms.ComboBox(); + this.grbConnect = new System.Windows.Forms.GroupBox(); + this.cmbAirTempChannel = new System.Windows.Forms.ComboBox(); + this.cmbWaterTempChannel = new System.Windows.Forms.ComboBox(); + this.cmbHumdityChannel = new System.Windows.Forms.ComboBox(); + this.cmbPressureChannel = new System.Windows.Forms.ComboBox(); + this.lblAirTemperatureChannel = new System.Windows.Forms.Label(); + this.lblWaterTemperatureChannel = new System.Windows.Forms.Label(); + this.lblHumidityChannel = new System.Windows.Forms.Label(); + this.lblPressureChannel = new System.Windows.Forms.Label(); + this.grpConfiguration = new System.Windows.Forms.GroupBox(); + this.lblInformation = new System.Windows.Forms.Label(); + this.grbInformation = new System.Windows.Forms.GroupBox(); + this.timRefresh = new System.Windows.Forms.Timer(this.components); + this.btnClose = new System.Windows.Forms.Button(); + this.grbCapture.SuspendLayout(); + this.grbConfigureCalibry.SuspendLayout(); + this.grbConnect.SuspendLayout(); + this.grpConfiguration.SuspendLayout(); + this.grbInformation.SuspendLayout(); + this.SuspendLayout(); + // + // lblAirTemperature + // + resources.ApplyResources(this.lblAirTemperature, "lblAirTemperature"); + this.lblAirTemperature.Name = "lblAirTemperature"; + // + // lblWaterTemperature + // + resources.ApplyResources(this.lblWaterTemperature, "lblWaterTemperature"); + this.lblWaterTemperature.Name = "lblWaterTemperature"; + // + // lblHumidity + // + resources.ApplyResources(this.lblHumidity, "lblHumidity"); + this.lblHumidity.Name = "lblHumidity"; + // + // lblPressure + // + resources.ApplyResources(this.lblPressure, "lblPressure"); + this.lblPressure.Name = "lblPressure"; + // + // txtAirTemperature + // + this.txtAirTemperature.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtAirTemperature, "txtAirTemperature"); + this.txtAirTemperature.Name = "txtAirTemperature"; + this.txtAirTemperature.ReadOnly = true; + // + // txtPressure + // + this.txtPressure.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtPressure, "txtPressure"); + this.txtPressure.Name = "txtPressure"; + this.txtPressure.ReadOnly = true; + // + // txtHumidity + // + this.txtHumidity.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtHumidity, "txtHumidity"); + this.txtHumidity.Name = "txtHumidity"; + this.txtHumidity.ReadOnly = true; + // + // txtWaterTemperature + // + this.txtWaterTemperature.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtWaterTemperature, "txtWaterTemperature"); + this.txtWaterTemperature.Name = "txtWaterTemperature"; + this.txtWaterTemperature.ReadOnly = true; + // + // lblDeviceSN + // + resources.ApplyResources(this.lblDeviceSN, "lblDeviceSN"); + this.lblDeviceSN.Name = "lblDeviceSN"; + // + // lblCaptureTime + // + resources.ApplyResources(this.lblCaptureTime, "lblCaptureTime"); + this.lblCaptureTime.Name = "lblCaptureTime"; + // + // txtDeviceSN + // + this.txtDeviceSN.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtDeviceSN, "txtDeviceSN"); + this.txtDeviceSN.Name = "txtDeviceSN"; + this.txtDeviceSN.ReadOnly = true; + // + // txtCaptureTime + // + this.txtCaptureTime.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtCaptureTime, "txtCaptureTime"); + this.txtCaptureTime.Name = "txtCaptureTime"; + this.txtCaptureTime.ReadOnly = true; + // + // lblDeviceModel + // + resources.ApplyResources(this.lblDeviceModel, "lblDeviceModel"); + this.lblDeviceModel.Name = "lblDeviceModel"; + // + // txtDeviceModel + // + this.txtDeviceModel.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtDeviceModel, "txtDeviceModel"); + this.txtDeviceModel.Name = "txtDeviceModel"; + this.txtDeviceModel.ReadOnly = true; + // + // lblConnectionStatus + // + resources.ApplyResources(this.lblConnectionStatus, "lblConnectionStatus"); + this.lblConnectionStatus.Name = "lblConnectionStatus"; + // + // txtConnectionStatus + // + this.txtConnectionStatus.BackColor = System.Drawing.SystemColors.Control; + resources.ApplyResources(this.txtConnectionStatus, "txtConnectionStatus"); + this.txtConnectionStatus.Name = "txtConnectionStatus"; + this.txtConnectionStatus.ReadOnly = true; + // + // grbCapture + // + this.grbCapture.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.grbCapture.Controls.Add(this.txtConnectionStatus); + this.grbCapture.Controls.Add(this.lblConnectionStatus); + this.grbCapture.Controls.Add(this.txtDeviceModel); + this.grbCapture.Controls.Add(this.lblDeviceModel); + this.grbCapture.Controls.Add(this.txtCaptureTime); + this.grbCapture.Controls.Add(this.txtDeviceSN); + this.grbCapture.Controls.Add(this.lblCaptureTime); + this.grbCapture.Controls.Add(this.lblDeviceSN); + this.grbCapture.Controls.Add(this.txtWaterTemperature); + this.grbCapture.Controls.Add(this.txtHumidity); + this.grbCapture.Controls.Add(this.txtPressure); + this.grbCapture.Controls.Add(this.txtAirTemperature); + this.grbCapture.Controls.Add(this.lblPressure); + this.grbCapture.Controls.Add(this.lblHumidity); + this.grbCapture.Controls.Add(this.lblWaterTemperature); + this.grbCapture.Controls.Add(this.lblAirTemperature); + resources.ApplyResources(this.grbCapture, "grbCapture"); + this.grbCapture.Name = "grbCapture"; + this.grbCapture.TabStop = false; + // + // lblAutoReadingNameHeader + // + resources.ApplyResources(this.lblAutoReadingNameHeader, "lblAutoReadingNameHeader"); + this.lblAutoReadingNameHeader.Name = "lblAutoReadingNameHeader"; + // + // lblAutoReadingPortHeader + // + resources.ApplyResources(this.lblAutoReadingPortHeader, "lblAutoReadingPortHeader"); + this.lblAutoReadingPortHeader.Name = "lblAutoReadingPortHeader"; + // + // lblCalibrySetup + // + resources.ApplyResources(this.lblCalibrySetup, "lblCalibrySetup"); + this.lblCalibrySetup.Name = "lblCalibrySetup"; + // + // lblAutoReadingHeader + // + resources.ApplyResources(this.lblAutoReadingHeader, "lblAutoReadingHeader"); + this.lblAutoReadingHeader.Name = "lblAutoReadingHeader"; + // + // lblAutoReadingValue + // + resources.ApplyResources(this.lblAutoReadingValue, "lblAutoReadingValue"); + this.lblAutoReadingValue.Name = "lblAutoReadingValue"; + // + // lblAutoReadingNameValue + // + resources.ApplyResources(this.lblAutoReadingNameValue, "lblAutoReadingNameValue"); + this.lblAutoReadingNameValue.Name = "lblAutoReadingNameValue"; + // + // lblAutoReadingPortValue + // + resources.ApplyResources(this.lblAutoReadingPortValue, "lblAutoReadingPortValue"); + this.lblAutoReadingPortValue.Name = "lblAutoReadingPortValue"; + // + // grbConfigureCalibry + // + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingPortValue); + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingNameValue); + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingValue); + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingHeader); + this.grbConfigureCalibry.Controls.Add(this.lblCalibrySetup); + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingPortHeader); + this.grbConfigureCalibry.Controls.Add(this.lblAutoReadingNameHeader); + resources.ApplyResources(this.grbConfigureCalibry, "grbConfigureCalibry"); + this.grbConfigureCalibry.Name = "grbConfigureCalibry"; + this.grbConfigureCalibry.TabStop = false; + // + // cmbDeviceModel + // + this.cmbDeviceModel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbDeviceModel.FormattingEnabled = true; + resources.ApplyResources(this.cmbDeviceModel, "cmbDeviceModel"); + this.cmbDeviceModel.Name = "cmbDeviceModel"; + this.cmbDeviceModel.SelectedIndexChanged += new System.EventHandler(this.cmbDeviceModel_SelectedIndexChanged); + this.cmbDeviceModel.Click += new System.EventHandler(this.cmbDeviceModel_SelectedIndexChanged); + // + // lblDevice + // + resources.ApplyResources(this.lblDevice, "lblDevice"); + this.lblDevice.Name = "lblDevice"; + // + // btnConnect + // + resources.ApplyResources(this.btnConnect, "btnConnect"); + this.btnConnect.Name = "btnConnect"; + this.btnConnect.UseVisualStyleBackColor = true; + this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click); + // + // lblComPort + // + resources.ApplyResources(this.lblComPort, "lblComPort"); + this.lblComPort.Name = "lblComPort"; + // + // cmbCOMPort + // + this.cmbCOMPort.FormattingEnabled = true; + resources.ApplyResources(this.cmbCOMPort, "cmbCOMPort"); + this.cmbCOMPort.Name = "cmbCOMPort"; + // + // grbConnect + // + this.grbConnect.Controls.Add(this.cmbCOMPort); + this.grbConnect.Controls.Add(this.lblComPort); + this.grbConnect.Controls.Add(this.btnConnect); + this.grbConnect.Controls.Add(this.lblDevice); + this.grbConnect.Controls.Add(this.cmbDeviceModel); + resources.ApplyResources(this.grbConnect, "grbConnect"); + this.grbConnect.Name = "grbConnect"; + this.grbConnect.TabStop = false; + // + // cmbAirTempChannel + // + this.cmbAirTempChannel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbAirTempChannel.FormattingEnabled = true; + resources.ApplyResources(this.cmbAirTempChannel, "cmbAirTempChannel"); + this.cmbAirTempChannel.Name = "cmbAirTempChannel"; + this.cmbAirTempChannel.SelectedIndexChanged += new System.EventHandler(this.cmbChannel_SelectedIndexChanged); + // + // cmbWaterTempChannel + // + this.cmbWaterTempChannel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbWaterTempChannel.FormattingEnabled = true; + resources.ApplyResources(this.cmbWaterTempChannel, "cmbWaterTempChannel"); + this.cmbWaterTempChannel.Name = "cmbWaterTempChannel"; + this.cmbWaterTempChannel.SelectedIndexChanged += new System.EventHandler(this.cmbChannel_SelectedIndexChanged); + // + // cmbHumdityChannel + // + this.cmbHumdityChannel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbHumdityChannel.FormattingEnabled = true; + resources.ApplyResources(this.cmbHumdityChannel, "cmbHumdityChannel"); + this.cmbHumdityChannel.Name = "cmbHumdityChannel"; + this.cmbHumdityChannel.SelectedIndexChanged += new System.EventHandler(this.cmbChannel_SelectedIndexChanged); + // + // cmbPressureChannel + // + this.cmbPressureChannel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbPressureChannel.FormattingEnabled = true; + resources.ApplyResources(this.cmbPressureChannel, "cmbPressureChannel"); + this.cmbPressureChannel.Name = "cmbPressureChannel"; + this.cmbPressureChannel.SelectedIndexChanged += new System.EventHandler(this.cmbChannel_SelectedIndexChanged); + // + // lblAirTemperatureChannel + // + resources.ApplyResources(this.lblAirTemperatureChannel, "lblAirTemperatureChannel"); + this.lblAirTemperatureChannel.Name = "lblAirTemperatureChannel"; + // + // lblWaterTemperatureChannel + // + resources.ApplyResources(this.lblWaterTemperatureChannel, "lblWaterTemperatureChannel"); + this.lblWaterTemperatureChannel.Name = "lblWaterTemperatureChannel"; + // + // lblHumidityChannel + // + resources.ApplyResources(this.lblHumidityChannel, "lblHumidityChannel"); + this.lblHumidityChannel.Name = "lblHumidityChannel"; + // + // lblPressureChannel + // + resources.ApplyResources(this.lblPressureChannel, "lblPressureChannel"); + this.lblPressureChannel.Name = "lblPressureChannel"; + // + // grpConfiguration + // + this.grpConfiguration.Controls.Add(this.lblPressureChannel); + this.grpConfiguration.Controls.Add(this.lblHumidityChannel); + this.grpConfiguration.Controls.Add(this.lblWaterTemperatureChannel); + this.grpConfiguration.Controls.Add(this.lblAirTemperatureChannel); + this.grpConfiguration.Controls.Add(this.cmbPressureChannel); + this.grpConfiguration.Controls.Add(this.cmbHumdityChannel); + this.grpConfiguration.Controls.Add(this.cmbWaterTempChannel); + this.grpConfiguration.Controls.Add(this.cmbAirTempChannel); + resources.ApplyResources(this.grpConfiguration, "grpConfiguration"); + this.grpConfiguration.Name = "grpConfiguration"; + this.grpConfiguration.TabStop = false; + // + // lblInformation + // + resources.ApplyResources(this.lblInformation, "lblInformation"); + this.lblInformation.Name = "lblInformation"; + // + // grbInformation + // + this.grbInformation.Controls.Add(this.lblInformation); + resources.ApplyResources(this.grbInformation, "grbInformation"); + this.grbInformation.Name = "grbInformation"; + this.grbInformation.TabStop = false; + // + // timRefresh + // + this.timRefresh.Interval = 5000; + this.timRefresh.Tick += new System.EventHandler(this.timRefresh_Tick); + // + // btnClose + // + resources.ApplyResources(this.btnClose, "btnClose"); + this.btnClose.Name = "btnClose"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // ConfigurationForm + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.Controls.Add(this.grbConfigureCalibry); + this.Controls.Add(this.grbCapture); + this.Controls.Add(this.grbInformation); + this.Controls.Add(this.grbConnect); + this.Controls.Add(this.grpConfiguration); + this.Controls.Add(this.btnClose); + this.Name = "ConfigurationForm"; + this.Load += new System.EventHandler(this.ConfigurationForm_Load); + this.grbCapture.ResumeLayout(false); + this.grbCapture.PerformLayout(); + this.grbConfigureCalibry.ResumeLayout(false); + this.grbConfigureCalibry.PerformLayout(); + this.grbConnect.ResumeLayout(false); + this.grpConfiguration.ResumeLayout(false); + this.grbInformation.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label lblAirTemperature; + private System.Windows.Forms.Label lblWaterTemperature; + private System.Windows.Forms.Label lblHumidity; + private System.Windows.Forms.Label lblPressure; + private System.Windows.Forms.TextBox txtAirTemperature; + private System.Windows.Forms.TextBox txtPressure; + private System.Windows.Forms.TextBox txtHumidity; + private System.Windows.Forms.TextBox txtWaterTemperature; + private System.Windows.Forms.Label lblDeviceSN; + private System.Windows.Forms.Label lblCaptureTime; + private System.Windows.Forms.TextBox txtDeviceSN; + private System.Windows.Forms.TextBox txtCaptureTime; + private System.Windows.Forms.Label lblDeviceModel; + private System.Windows.Forms.TextBox txtDeviceModel; + private System.Windows.Forms.Label lblConnectionStatus; + private System.Windows.Forms.TextBox txtConnectionStatus; + private System.Windows.Forms.GroupBox grbCapture; + private System.Windows.Forms.Label lblAutoReadingNameHeader; + private System.Windows.Forms.Label lblAutoReadingPortHeader; + private System.Windows.Forms.Label lblCalibrySetup; + private System.Windows.Forms.Label lblAutoReadingHeader; + private System.Windows.Forms.Label lblAutoReadingValue; + private System.Windows.Forms.Label lblAutoReadingNameValue; + private System.Windows.Forms.Label lblAutoReadingPortValue; + private System.Windows.Forms.GroupBox grbConfigureCalibry; + private System.Windows.Forms.ComboBox cmbDeviceModel; + private System.Windows.Forms.Label lblDevice; + private System.Windows.Forms.Button btnConnect; + private System.Windows.Forms.Label lblComPort; + private System.Windows.Forms.ComboBox cmbCOMPort; + private System.Windows.Forms.GroupBox grbConnect; + private System.Windows.Forms.ComboBox cmbAirTempChannel; + private System.Windows.Forms.ComboBox cmbWaterTempChannel; + private System.Windows.Forms.ComboBox cmbHumdityChannel; + private System.Windows.Forms.ComboBox cmbPressureChannel; + private System.Windows.Forms.Label lblAirTemperatureChannel; + private System.Windows.Forms.Label lblWaterTemperatureChannel; + private System.Windows.Forms.Label lblHumidityChannel; + private System.Windows.Forms.Label lblPressureChannel; + private System.Windows.Forms.GroupBox grpConfiguration; + private System.Windows.Forms.Label lblInformation; + private System.Windows.Forms.GroupBox grbInformation; + private System.Windows.Forms.Timer timRefresh; + private System.Windows.Forms.Button btnClose; + + + + } +} \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.cs new file mode 100644 index 0000000..c8ce40f --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace TestoCaptureTool +{ + public partial class ConfigurationForm : Form + { + public ConfigurationForm() + { + InitializeComponent(); + Program._device.ConnectionStatusChanged += new EventHandler(Device_ConnectionStatusChanged); + } + + /// <summary> + /// Update the information according to the status of the device + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + void Device_ConnectionStatusChanged(object sender, EventArgs e) + { + UpdateDeviceStatus(); + } + + + /// <summary> + /// Get the environmental conditions from the device and display it to the user. + /// </summary> + private void GetValue() + { + txtAirTemperature.Text = Program._device.AirTemperature.ToString("0.00",System.Globalization.CultureInfo.CurrentUICulture); + txtWaterTemperature.Text = Program._device.WaterTemperature.ToString("0.00", System.Globalization.CultureInfo.CurrentUICulture); + txtPressure.Text = Program._device.Pressure.ToString("0.00", System.Globalization.CultureInfo.CurrentUICulture); + txtHumidity.Text = Program._device.Humidity.ToString("0.00", System.Globalization.CultureInfo.CurrentUICulture); + txtDeviceSN.Text = Program._device.DeviceSN.ToString(System.Globalization.CultureInfo.CurrentUICulture); + txtCaptureTime.Text = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString(); + txtDeviceModel.Text = cmbDeviceModel.Text; + } + + /// <summary> + /// Refresh the environmental conditions (automatic) + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + private void timRefresh_Tick(object sender, EventArgs e) + { + GetValue(); + } + + private void btnClose_Click(object sender, EventArgs e) + { + this.Close(); + } + + /// <summary> + /// Fill the list of the supported devices model displayed to the user + /// Selects the current device from the list (if defined) + /// Display the device status to the user + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + private void ConfigurationForm_Load(object sender, EventArgs e) + { + cmbCOMPort.Items.Clear(); + cmbCOMPort.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); + cmbDeviceModel.Items.Add(new CaptionObject("testo 435-635-735", "testo435-635-735")); + cmbDeviceModel.DisplayMember = "Caption"; + cmbDeviceModel.ValueMember = "Name"; + + foreach (CaptionObject item in cmbDeviceModel.Items) + { + if (item.Name == Program._device.DeviceType) + { + cmbDeviceModel.SelectedItem = item; + break; + } + + } + + foreach (string port in cmbCOMPort.Items) + { + if(String.Compare(port.Remove(0,3),Program._device.ComPort.ToString())==0) + { + cmbCOMPort.SelectedItem=port; + break; + } + } + + UpdateDeviceStatus(); + lblAutoReadingNameValue.Text = System.Net.Dns.GetHostName(); + lblAutoReadingPortValue.Text = Properties.Settings.Default.NetworkPort.ToString(); + + + } + + /// <summary> + /// When the user select an other device in the list, + /// defining the selection as current model. + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + private void cmbDeviceModel_SelectedIndexChanged(object sender, EventArgs e) + { + if (cmbDeviceModel.SelectedItem != null) + { + CaptionObject selectedDevice = (CaptionObject)cmbDeviceModel.SelectedItem; + + if (Program._device != null) + { + if (TestoDevice.IsUsbDevice(selectedDevice.Name)) + { + cmbCOMPort.Enabled = false; + cmbCOMPort.DropDownStyle = ComboBoxStyle.DropDown; + cmbCOMPort.Text = "USB"; + } + else + { + cmbCOMPort.DropDownStyle = ComboBoxStyle.DropDownList; + cmbCOMPort.Enabled = true; + cmbCOMPort.SelectedIndex = 0; + } + } + } + } + + /// <summary> + /// Updating the information related to the status of the device to the user + /// </summary> + private void UpdateDeviceStatus() + { + timRefresh.Enabled = Program._device.IsConnected; + + //The list of available channel is cleared and filled with the new settings. + cmbAirTempChannel.Items.Clear(); + cmbWaterTempChannel.Items.Clear(); + cmbPressureChannel.Items.Clear(); + cmbHumdityChannel.Items.Clear(); + + if (Program._device.IsConnected) + { + CaptionObject channelItem = new CaptionObject(Properties.Resources.NotAvailable, "-1"); + cmbAirTempChannel.Items.Add(channelItem); + cmbWaterTempChannel.Items.Add(channelItem); + cmbPressureChannel.Items.Add(channelItem); + cmbHumdityChannel.Items.Add(channelItem); + + for (int channel = 0; channel < Program._device.AvailableChannelCount; channel++) + { + channelItem = new CaptionObject(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Properties.Resources.channelNumber, channel + 1), channel.ToString(System.Globalization.CultureInfo.InvariantCulture)); + cmbAirTempChannel.Items.Add(channelItem); + cmbWaterTempChannel.Items.Add(channelItem); + cmbPressureChannel.Items.Add(channelItem); + cmbHumdityChannel.Items.Add(channelItem); + } + + //Selecting the value for each channel if already defined + cmbAirTempChannel.SelectedIndex = Program._device.AirTemperatureChannel+1; + cmbWaterTempChannel.SelectedIndex = Program._device.WaterTemperatureChannel+1; + cmbHumdityChannel.SelectedIndex = Program._device.HumidityChannel+1; + cmbPressureChannel.SelectedIndex = Program._device.PressureChannel+1; + + //Displaying the current value retrieved on the device + GetValue(); + + txtConnectionStatus.ForeColor = Color.Green; + txtConnectionStatus.Text = Properties.Resources.deviceConnected; + } + cmbAirTempChannel.DisplayMember = "Caption"; + cmbWaterTempChannel.DisplayMember = "Caption"; + cmbPressureChannel.DisplayMember = "Caption"; + cmbHumdityChannel.DisplayMember = "Caption"; + + cmbAirTempChannel.ValueMember = "Name"; + cmbWaterTempChannel.ValueMember = "Name"; + cmbPressureChannel.ValueMember = "Name"; + cmbHumdityChannel.ValueMember = "Name"; + } + + /// <summary> + /// Connecting to the device with the specified model type and + /// specified COM Port (if not USB device) + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + private void btnConnect_Click(object sender, EventArgs e) + { + CaptionObject selectedDevice = (CaptionObject)cmbDeviceModel.SelectedItem; + + if (selectedDevice == null) + { + MessageBox.Show(Properties.Resources.selectDevice, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + + Program._device.Disconnect(); + Program._device.DeviceType = selectedDevice.Name; + if (!TestoDevice.IsUsbDevice(selectedDevice.Name)) + { + short comPortOut; + string comPort = cmbCOMPort.Text.Remove(0, 3); + if (!short.TryParse(comPort, out comPortOut)) + { + MessageBox.Show(Properties.Resources.specifyCOMPort, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + + Program._device.ComPort = comPortOut; + } + + TestoDevice.OperationResult result = Program._device.Connect(); + + if (result != TestoDevice.OperationResult.Ok) + { + txtConnectionStatus.ForeColor = Color.Red; + txtConnectionStatus.Text = TestoDevice.ErrorMessage(result); + txtAirTemperature.Text = ""; + txtWaterTemperature.Text = ""; + txtPressure.Text = ""; + txtHumidity.Text = ""; + txtDeviceSN.Text = ""; + txtCaptureTime.Text = ""; + MessageBox.Show(TestoDevice.ErrorMessage(result), this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1); + } + + return; + } + + private void cmbChannel_SelectedIndexChanged(object sender, EventArgs e) + { + ComboBox cmbSender = (ComboBox)sender; + if (cmbSender == cmbAirTempChannel) + Program._device.AirTemperatureChannel = System.Convert.ToInt16(((CaptionObject)cmbSender.SelectedItem).Name); + + if(cmbSender==cmbWaterTempChannel) + Program._device.WaterTemperatureChannel = System.Convert.ToInt16(((CaptionObject)cmbSender.SelectedItem).Name); + + if(cmbSender==cmbHumdityChannel) + Program._device.HumidityChannel = System.Convert.ToInt16(((CaptionObject)cmbSender.SelectedItem).Name); + + if(cmbSender==cmbPressureChannel) + Program._device.PressureChannel = System.Convert.ToInt16(((CaptionObject)cmbSender.SelectedItem).Name); + + GetValue(); + } + } +} \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.resx b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.resx new file mode 100644 index 0000000..ff111ed --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/ConfigurationForm.resx @@ -0,0 +1,1287 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <data name="lblAirTemperature.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> + <data name="lblAirTemperature.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 154</value> + </data> + <data name="lblAirTemperature.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblAirTemperature.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <assembly alias="mscorlib" name="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <data name="lblAirTemperature.TabIndex" type="System.Int32, mscorlib"> + <value>19</value> + </data> + <data name="lblAirTemperature.Text" xml:space="preserve"> + <value>Air temperature in °C</value> + </data> + <data name=">>lblAirTemperature.Name" xml:space="preserve"> + <value>lblAirTemperature</value> + </data> + <data name=">>lblAirTemperature.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAirTemperature.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblAirTemperature.ZOrder" xml:space="preserve"> + <value>15</value> + </data> + <data name="lblWaterTemperature.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblWaterTemperature.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 180</value> + </data> + <data name="lblWaterTemperature.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblWaterTemperature.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblWaterTemperature.TabIndex" type="System.Int32, mscorlib"> + <value>20</value> + </data> + <data name="lblWaterTemperature.Text" xml:space="preserve"> + <value>Water temperature in °C</value> + </data> + <data name=">>lblWaterTemperature.Name" xml:space="preserve"> + <value>lblWaterTemperature</value> + </data> + <data name=">>lblWaterTemperature.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblWaterTemperature.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblWaterTemperature.ZOrder" xml:space="preserve"> + <value>14</value> + </data> + <data name="lblHumidity.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblHumidity.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 205</value> + </data> + <data name="lblHumidity.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblHumidity.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblHumidity.TabIndex" type="System.Int32, mscorlib"> + <value>21</value> + </data> + <data name="lblHumidity.Text" xml:space="preserve"> + <value>Humidity in %</value> + </data> + <data name=">>lblHumidity.Name" xml:space="preserve"> + <value>lblHumidity</value> + </data> + <data name=">>lblHumidity.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblHumidity.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblHumidity.ZOrder" xml:space="preserve"> + <value>13</value> + </data> + <data name="lblPressure.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblPressure.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 231</value> + </data> + <data name="lblPressure.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblPressure.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblPressure.TabIndex" type="System.Int32, mscorlib"> + <value>22</value> + </data> + <data name="lblPressure.Text" xml:space="preserve"> + <value>Pressure in kPa</value> + </data> + <data name=">>lblPressure.Name" xml:space="preserve"> + <value>lblPressure</value> + </data> + <data name=">>lblPressure.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblPressure.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblPressure.ZOrder" xml:space="preserve"> + <value>12</value> + </data> + <data name="txtAirTemperature.Location" type="System.Drawing.Point, System.Drawing"> + <value>178, 151</value> + </data> + <data name="txtAirTemperature.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtAirTemperature.TabIndex" type="System.Int32, mscorlib"> + <value>24</value> + </data> + <data name=">>txtAirTemperature.Name" xml:space="preserve"> + <value>txtAirTemperature</value> + </data> + <data name=">>txtAirTemperature.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtAirTemperature.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtAirTemperature.ZOrder" xml:space="preserve"> + <value>11</value> + </data> + <data name="txtPressure.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 228</value> + </data> + <data name="txtPressure.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtPressure.TabIndex" type="System.Int32, mscorlib"> + <value>25</value> + </data> + <data name=">>txtPressure.Name" xml:space="preserve"> + <value>txtPressure</value> + </data> + <data name=">>txtPressure.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtPressure.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtPressure.ZOrder" xml:space="preserve"> + <value>10</value> + </data> + <data name="txtHumidity.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 202</value> + </data> + <data name="txtHumidity.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtHumidity.TabIndex" type="System.Int32, mscorlib"> + <value>26</value> + </data> + <data name=">>txtHumidity.Name" xml:space="preserve"> + <value>txtHumidity</value> + </data> + <data name=">>txtHumidity.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtHumidity.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtHumidity.ZOrder" xml:space="preserve"> + <value>9</value> + </data> + <data name="txtWaterTemperature.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 177</value> + </data> + <data name="txtWaterTemperature.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtWaterTemperature.TabIndex" type="System.Int32, mscorlib"> + <value>27</value> + </data> + <data name=">>txtWaterTemperature.Name" xml:space="preserve"> + <value>txtWaterTemperature</value> + </data> + <data name=">>txtWaterTemperature.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtWaterTemperature.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtWaterTemperature.ZOrder" xml:space="preserve"> + <value>8</value> + </data> + <data name="lblDeviceSN.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblDeviceSN.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 80</value> + </data> + <data name="lblDeviceSN.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblDeviceSN.TabIndex" type="System.Int32, mscorlib"> + <value>28</value> + </data> + <data name="lblDeviceSN.Text" xml:space="preserve"> + <value>Device SN</value> + </data> + <data name=">>lblDeviceSN.Name" xml:space="preserve"> + <value>lblDeviceSN</value> + </data> + <data name=">>lblDeviceSN.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblDeviceSN.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblDeviceSN.ZOrder" xml:space="preserve"> + <value>7</value> + </data> + <data name="lblCaptureTime.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblCaptureTime.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 128</value> + </data> + <data name="lblCaptureTime.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblCaptureTime.TabIndex" type="System.Int32, mscorlib"> + <value>29</value> + </data> + <data name="lblCaptureTime.Text" xml:space="preserve"> + <value>Capture time</value> + </data> + <data name=">>lblCaptureTime.Name" xml:space="preserve"> + <value>lblCaptureTime</value> + </data> + <data name=">>lblCaptureTime.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblCaptureTime.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblCaptureTime.ZOrder" xml:space="preserve"> + <value>6</value> + </data> + <data name="txtDeviceSN.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 77</value> + </data> + <data name="txtDeviceSN.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtDeviceSN.TabIndex" type="System.Int32, mscorlib"> + <value>30</value> + </data> + <data name=">>txtDeviceSN.Name" xml:space="preserve"> + <value>txtDeviceSN</value> + </data> + <data name=">>txtDeviceSN.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtDeviceSN.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtDeviceSN.ZOrder" xml:space="preserve"> + <value>5</value> + </data> + <data name="txtCaptureTime.Location" type="System.Drawing.Point, System.Drawing"> + <value>178, 125</value> + </data> + <data name="txtCaptureTime.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtCaptureTime.TabIndex" type="System.Int32, mscorlib"> + <value>31</value> + </data> + <data name=">>txtCaptureTime.Name" xml:space="preserve"> + <value>txtCaptureTime</value> + </data> + <data name=">>txtCaptureTime.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtCaptureTime.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtCaptureTime.ZOrder" xml:space="preserve"> + <value>4</value> + </data> + <data name="lblDeviceModel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblDeviceModel.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 28</value> + </data> + <data name="lblDeviceModel.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblDeviceModel.TabIndex" type="System.Int32, mscorlib"> + <value>32</value> + </data> + <data name="lblDeviceModel.Text" xml:space="preserve"> + <value>Device model</value> + </data> + <data name=">>lblDeviceModel.Name" xml:space="preserve"> + <value>lblDeviceModel</value> + </data> + <data name=">>lblDeviceModel.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblDeviceModel.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblDeviceModel.ZOrder" xml:space="preserve"> + <value>3</value> + </data> + <data name="txtDeviceModel.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 25</value> + </data> + <data name="txtDeviceModel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtDeviceModel.TabIndex" type="System.Int32, mscorlib"> + <value>33</value> + </data> + <data name=">>txtDeviceModel.Name" xml:space="preserve"> + <value>txtDeviceModel</value> + </data> + <data name=">>txtDeviceModel.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtDeviceModel.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtDeviceModel.ZOrder" xml:space="preserve"> + <value>2</value> + </data> + <data name="lblConnectionStatus.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblConnectionStatus.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 54</value> + </data> + <data name="lblConnectionStatus.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblConnectionStatus.TabIndex" type="System.Int32, mscorlib"> + <value>34</value> + </data> + <data name="lblConnectionStatus.Text" xml:space="preserve"> + <value>Connection status</value> + </data> + <data name=">>lblConnectionStatus.Name" xml:space="preserve"> + <value>lblConnectionStatus</value> + </data> + <data name=">>lblConnectionStatus.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblConnectionStatus.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>lblConnectionStatus.ZOrder" xml:space="preserve"> + <value>1</value> + </data> + <data name="txtConnectionStatus.Location" type="System.Drawing.Point, System.Drawing"> + <value>179, 51</value> + </data> + <data name="txtConnectionStatus.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 20</value> + </data> + <data name="txtConnectionStatus.TabIndex" type="System.Int32, mscorlib"> + <value>35</value> + </data> + <data name=">>txtConnectionStatus.Name" xml:space="preserve"> + <value>txtConnectionStatus</value> + </data> + <data name=">>txtConnectionStatus.Type" xml:space="preserve"> + <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>txtConnectionStatus.Parent" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>txtConnectionStatus.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="grbCapture.Location" type="System.Drawing.Point, System.Drawing"> + <value>465, 12</value> + </data> + <data name="grbCapture.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="grbCapture.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="grbCapture.Size" type="System.Drawing.Size, System.Drawing"> + <value>450, 268</value> + </data> + <data name="grbCapture.TabIndex" type="System.Int32, mscorlib"> + <value>27</value> + </data> + <data name="grbCapture.Text" xml:space="preserve"> + <value>3. Device status</value> + </data> + <data name=">>grbCapture.Name" xml:space="preserve"> + <value>grbCapture</value> + </data> + <data name=">>grbCapture.Type" xml:space="preserve"> + <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>grbCapture.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>grbCapture.ZOrder" xml:space="preserve"> + <value>1</value> + </data> + <data name="lblAutoReadingNameHeader.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingNameHeader.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingNameHeader.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 78</value> + </data> + <data name="lblAutoReadingNameHeader.Size" type="System.Drawing.Size, System.Drawing"> + <value>131, 13</value> + </data> + <data name="lblAutoReadingNameHeader.TabIndex" type="System.Int32, mscorlib"> + <value>48</value> + </data> + <data name="lblAutoReadingNameHeader.Text" xml:space="preserve"> + <value>Auto reading remote name</value> + </data> + <data name=">>lblAutoReadingNameHeader.Name" xml:space="preserve"> + <value>lblAutoReadingNameHeader</value> + </data> + <data name=">>lblAutoReadingNameHeader.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingNameHeader.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingNameHeader.ZOrder" xml:space="preserve"> + <value>6</value> + </data> + <data name="lblAutoReadingPortHeader.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingPortHeader.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingPortHeader.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 103</value> + </data> + <data name="lblAutoReadingPortHeader.Size" type="System.Drawing.Size, System.Drawing"> + <value>123, 13</value> + </data> + <data name="lblAutoReadingPortHeader.TabIndex" type="System.Int32, mscorlib"> + <value>49</value> + </data> + <data name="lblAutoReadingPortHeader.Text" xml:space="preserve"> + <value>Auto reading remote port</value> + </data> + <data name=">>lblAutoReadingPortHeader.Name" xml:space="preserve"> + <value>lblAutoReadingPortHeader</value> + </data> + <data name=">>lblAutoReadingPortHeader.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingPortHeader.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingPortHeader.ZOrder" xml:space="preserve"> + <value>5</value> + </data> + <data name="lblCalibrySetup.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblCalibrySetup.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblCalibrySetup.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 28</value> + </data> + <data name="lblCalibrySetup.Size" type="System.Drawing.Size, System.Drawing"> + <value>325, 13</value> + </data> + <data name="lblCalibrySetup.TabIndex" type="System.Int32, mscorlib"> + <value>50</value> + </data> + <data name="lblCalibrySetup.Text" xml:space="preserve"> + <value>Options >> Configuration >> Calibration >> Environmental parameter</value> + </data> + <data name=">>lblCalibrySetup.Name" xml:space="preserve"> + <value>lblCalibrySetup</value> + </data> + <data name=">>lblCalibrySetup.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblCalibrySetup.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblCalibrySetup.ZOrder" xml:space="preserve"> + <value>4</value> + </data> + <data name="lblAutoReadingHeader.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingHeader.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingHeader.Location" type="System.Drawing.Point, System.Drawing"> + <value>26, 53</value> + </data> + <data name="lblAutoReadingHeader.Size" type="System.Drawing.Size, System.Drawing"> + <value>102, 13</value> + </data> + <data name="lblAutoReadingHeader.TabIndex" type="System.Int32, mscorlib"> + <value>52</value> + </data> + <data name="lblAutoReadingHeader.Text" xml:space="preserve"> + <value>Enable auto reading</value> + </data> + <data name=">>lblAutoReadingHeader.Name" xml:space="preserve"> + <value>lblAutoReadingHeader</value> + </data> + <data name=">>lblAutoReadingHeader.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingHeader.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingHeader.ZOrder" xml:space="preserve"> + <value>3</value> + </data> + <data name="lblAutoReadingValue.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingValue.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingValue.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 53</value> + </data> + <data name="lblAutoReadingValue.Size" type="System.Drawing.Size, System.Drawing"> + <value>25, 13</value> + </data> + <data name="lblAutoReadingValue.TabIndex" type="System.Int32, mscorlib"> + <value>54</value> + </data> + <data name="lblAutoReadingValue.Text" xml:space="preserve"> + <value>Yes</value> + </data> + <data name=">>lblAutoReadingValue.Name" xml:space="preserve"> + <value>lblAutoReadingValue</value> + </data> + <data name=">>lblAutoReadingValue.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingValue.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingValue.ZOrder" xml:space="preserve"> + <value>2</value> + </data> + <data name="lblAutoReadingNameValue.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingNameValue.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingNameValue.Location" type="System.Drawing.Point, System.Drawing"> + <value>176, 78</value> + </data> + <data name="lblAutoReadingNameValue.Size" type="System.Drawing.Size, System.Drawing"> + <value>35, 13</value> + </data> + <data name="lblAutoReadingNameValue.TabIndex" type="System.Int32, mscorlib"> + <value>55</value> + </data> + <data name="lblAutoReadingNameValue.Text" xml:space="preserve"> + <value>label8</value> + </data> + <data name=">>lblAutoReadingNameValue.Name" xml:space="preserve"> + <value>lblAutoReadingNameValue</value> + </data> + <data name=">>lblAutoReadingNameValue.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingNameValue.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingNameValue.ZOrder" xml:space="preserve"> + <value>1</value> + </data> + <data name="lblAutoReadingPortValue.AutoSize" type="System.Boolean, mscorlib"> + <value>True</value> + </data> + <data name="lblAutoReadingPortValue.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAutoReadingPortValue.Location" type="System.Drawing.Point, System.Drawing"> + <value>176, 103</value> + </data> + <data name="lblAutoReadingPortValue.Size" type="System.Drawing.Size, System.Drawing"> + <value>35, 13</value> + </data> + <data name="lblAutoReadingPortValue.TabIndex" type="System.Int32, mscorlib"> + <value>56</value> + </data> + <data name="lblAutoReadingPortValue.Text" xml:space="preserve"> + <value>label9</value> + </data> + <data name=">>lblAutoReadingPortValue.Name" xml:space="preserve"> + <value>lblAutoReadingPortValue</value> + </data> + <data name=">>lblAutoReadingPortValue.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAutoReadingPortValue.Parent" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>lblAutoReadingPortValue.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="grbConfigureCalibry.Location" type="System.Drawing.Point, System.Drawing"> + <value>465, 290</value> + </data> + <data name="grbConfigureCalibry.Size" type="System.Drawing.Size, System.Drawing"> + <value>450, 136</value> + </data> + <data name="grbConfigureCalibry.TabIndex" type="System.Int32, mscorlib"> + <value>28</value> + </data> + <data name="grbConfigureCalibry.Text" xml:space="preserve"> + <value>4. Configure your calibry software</value> + </data> + <data name=">>grbConfigureCalibry.Name" xml:space="preserve"> + <value>grbConfigureCalibry</value> + </data> + <data name=">>grbConfigureCalibry.Type" xml:space="preserve"> + <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>grbConfigureCalibry.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>grbConfigureCalibry.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="cmbDeviceModel.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 25</value> + </data> + <data name="cmbDeviceModel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbDeviceModel.TabIndex" type="System.Int32, mscorlib"> + <value>0</value> + </data> + <data name=">>cmbDeviceModel.Name" xml:space="preserve"> + <value>cmbDeviceModel</value> + </data> + <data name=">>cmbDeviceModel.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbDeviceModel.Parent" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>cmbDeviceModel.ZOrder" xml:space="preserve"> + <value>4</value> + </data> + <data name="lblDevice.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblDevice.Location" type="System.Drawing.Point, System.Drawing"> + <value>19, 28</value> + </data> + <data name="lblDevice.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblDevice.TabIndex" type="System.Int32, mscorlib"> + <value>1</value> + </data> + <data name="lblDevice.Text" xml:space="preserve"> + <value>Device model</value> + </data> + <data name=">>lblDevice.Name" xml:space="preserve"> + <value>lblDevice</value> + </data> + <data name=">>lblDevice.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblDevice.Parent" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>lblDevice.ZOrder" xml:space="preserve"> + <value>3</value> + </data> + <data name="btnConnect.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="btnConnect.Location" type="System.Drawing.Point, System.Drawing"> + <value>345, 80</value> + </data> + <data name="btnConnect.Size" type="System.Drawing.Size, System.Drawing"> + <value>75, 23</value> + </data> + <data name="btnConnect.TabIndex" type="System.Int32, mscorlib"> + <value>2</value> + </data> + <data name="btnConnect.Text" xml:space="preserve"> + <value>Connect</value> + </data> + <data name=">>btnConnect.Name" xml:space="preserve"> + <value>btnConnect</value> + </data> + <data name=">>btnConnect.Type" xml:space="preserve"> + <value>System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>btnConnect.Parent" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>btnConnect.ZOrder" xml:space="preserve"> + <value>2</value> + </data> + <data name="lblComPort.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblComPort.Location" type="System.Drawing.Point, System.Drawing"> + <value>19, 53</value> + </data> + <data name="lblComPort.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblComPort.TabIndex" type="System.Int32, mscorlib"> + <value>3</value> + </data> + <data name="lblComPort.Text" xml:space="preserve"> + <value>COM Port</value> + </data> + <data name=">>lblComPort.Name" xml:space="preserve"> + <value>lblComPort</value> + </data> + <data name=">>lblComPort.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblComPort.Parent" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>lblComPort.ZOrder" xml:space="preserve"> + <value>1</value> + </data> + <data name="cmbCOMPort.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 50</value> + </data> + <data name="cmbCOMPort.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbCOMPort.TabIndex" type="System.Int32, mscorlib"> + <value>4</value> + </data> + <data name=">>cmbCOMPort.Name" xml:space="preserve"> + <value>cmbCOMPort</value> + </data> + <data name=">>cmbCOMPort.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbCOMPort.Parent" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>cmbCOMPort.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="grbConnect.Location" type="System.Drawing.Point, System.Drawing"> + <value>12, 161</value> + </data> + <data name="grbConnect.Size" type="System.Drawing.Size, System.Drawing"> + <value>450, 119</value> + </data> + <data name="grbConnect.TabIndex" type="System.Int32, mscorlib"> + <value>24</value> + </data> + <data name="grbConnect.Text" xml:space="preserve"> + <value>1. Configure your device</value> + </data> + <data name=">>grbConnect.Name" xml:space="preserve"> + <value>grbConnect</value> + </data> + <data name=">>grbConnect.Type" xml:space="preserve"> + <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>grbConnect.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>grbConnect.ZOrder" xml:space="preserve"> + <value>3</value> + </data> + <data name="cmbAirTempChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 25</value> + </data> + <data name="cmbAirTempChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 4</value> + </data> + <data name="cmbAirTempChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbAirTempChannel.TabIndex" type="System.Int32, mscorlib"> + <value>10</value> + </data> + <data name=">>cmbAirTempChannel.Name" xml:space="preserve"> + <value>cmbAirTempChannel</value> + </data> + <data name=">>cmbAirTempChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbAirTempChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>cmbAirTempChannel.ZOrder" xml:space="preserve"> + <value>7</value> + </data> + <data name="cmbWaterTempChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 50</value> + </data> + <data name="cmbWaterTempChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 4</value> + </data> + <data name="cmbWaterTempChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbWaterTempChannel.TabIndex" type="System.Int32, mscorlib"> + <value>11</value> + </data> + <data name=">>cmbWaterTempChannel.Name" xml:space="preserve"> + <value>cmbWaterTempChannel</value> + </data> + <data name=">>cmbWaterTempChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbWaterTempChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>cmbWaterTempChannel.ZOrder" xml:space="preserve"> + <value>6</value> + </data> + <data name="cmbHumdityChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 75</value> + </data> + <data name="cmbHumdityChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 4</value> + </data> + <data name="cmbHumdityChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbHumdityChannel.TabIndex" type="System.Int32, mscorlib"> + <value>12</value> + </data> + <data name=">>cmbHumdityChannel.Name" xml:space="preserve"> + <value>cmbHumdityChannel</value> + </data> + <data name=">>cmbHumdityChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbHumdityChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>cmbHumdityChannel.ZOrder" xml:space="preserve"> + <value>5</value> + </data> + <data name="cmbPressureChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>175, 100</value> + </data> + <data name="cmbPressureChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 25</value> + </data> + <data name="cmbPressureChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>245, 21</value> + </data> + <data name="cmbPressureChannel.TabIndex" type="System.Int32, mscorlib"> + <value>13</value> + </data> + <data name=">>cmbPressureChannel.Name" xml:space="preserve"> + <value>cmbPressureChannel</value> + </data> + <data name=">>cmbPressureChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>cmbPressureChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>cmbPressureChannel.ZOrder" xml:space="preserve"> + <value>4</value> + </data> + <data name="lblAirTemperatureChannel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblAirTemperatureChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>21, 28</value> + </data> + <data name="lblAirTemperatureChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblAirTemperatureChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblAirTemperatureChannel.TabIndex" type="System.Int32, mscorlib"> + <value>19</value> + </data> + <data name="lblAirTemperatureChannel.Text" xml:space="preserve"> + <value>Air temperature capture</value> + </data> + <data name=">>lblAirTemperatureChannel.Name" xml:space="preserve"> + <value>lblAirTemperatureChannel</value> + </data> + <data name=">>lblAirTemperatureChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblAirTemperatureChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>lblAirTemperatureChannel.ZOrder" xml:space="preserve"> + <value>3</value> + </data> + <data name="lblWaterTemperatureChannel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblWaterTemperatureChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>21, 53</value> + </data> + <data name="lblWaterTemperatureChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblWaterTemperatureChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblWaterTemperatureChannel.TabIndex" type="System.Int32, mscorlib"> + <value>20</value> + </data> + <data name="lblWaterTemperatureChannel.Text" xml:space="preserve"> + <value>Water temperature capture</value> + </data> + <data name=">>lblWaterTemperatureChannel.Name" xml:space="preserve"> + <value>lblWaterTemperatureChannel</value> + </data> + <data name=">>lblWaterTemperatureChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblWaterTemperatureChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>lblWaterTemperatureChannel.ZOrder" xml:space="preserve"> + <value>2</value> + </data> + <data name="lblHumidityChannel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblHumidityChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>21, 78</value> + </data> + <data name="lblHumidityChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblHumidityChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblHumidityChannel.TabIndex" type="System.Int32, mscorlib"> + <value>21</value> + </data> + <data name="lblHumidityChannel.Text" xml:space="preserve"> + <value>Humidity capture</value> + </data> + <data name=">>lblHumidityChannel.Name" xml:space="preserve"> + <value>lblHumidityChannel</value> + </data> + <data name=">>lblHumidityChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblHumidityChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>lblHumidityChannel.ZOrder" xml:space="preserve"> + <value>1</value> + </data> + <data name="lblPressureChannel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblPressureChannel.Location" type="System.Drawing.Point, System.Drawing"> + <value>21, 103</value> + </data> + <data name="lblPressureChannel.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="lblPressureChannel.Size" type="System.Drawing.Size, System.Drawing"> + <value>150, 13</value> + </data> + <data name="lblPressureChannel.TabIndex" type="System.Int32, mscorlib"> + <value>22</value> + </data> + <data name="lblPressureChannel.Text" xml:space="preserve"> + <value>Pressure capture</value> + </data> + <data name=">>lblPressureChannel.Name" xml:space="preserve"> + <value>lblPressureChannel</value> + </data> + <data name=">>lblPressureChannel.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblPressureChannel.Parent" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>lblPressureChannel.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="grpConfiguration.Location" type="System.Drawing.Point, System.Drawing"> + <value>12, 290</value> + </data> + <data name="grpConfiguration.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="grpConfiguration.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms"> + <value>0, 0, 0, 0</value> + </data> + <data name="grpConfiguration.Size" type="System.Drawing.Size, System.Drawing"> + <value>450, 136</value> + </data> + <data name="grpConfiguration.TabIndex" type="System.Int32, mscorlib"> + <value>21</value> + </data> + <data name="grpConfiguration.Text" xml:space="preserve"> + <value>2. Configure the device's channels</value> + </data> + <data name=">>grpConfiguration.Name" xml:space="preserve"> + <value>grpConfiguration</value> + </data> + <data name=">>grpConfiguration.Type" xml:space="preserve"> + <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>grpConfiguration.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>grpConfiguration.ZOrder" xml:space="preserve"> + <value>4</value> + </data> + <data name="lblInformation.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="lblInformation.Location" type="System.Drawing.Point, System.Drawing"> + <value>21, 25</value> + </data> + <data name="lblInformation.Size" type="System.Drawing.Size, System.Drawing"> + <value>399, 112</value> + </data> + <data name="lblInformation.TabIndex" type="System.Int32, mscorlib"> + <value>0</value> + </data> + <data name="lblInformation.Text" xml:space="preserve"> + <value>1. Select a device model from the list and click on "Connect" + to establish the connection. + +2. Set the correct channel for each environmental parameter. + +3. Check that the values on the right are plausible. Adjust the settings + in step 2 as needed.</value> + </data> + <data name=">>lblInformation.Name" xml:space="preserve"> + <value>lblInformation</value> + </data> + <data name=">>lblInformation.Type" xml:space="preserve"> + <value>System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>lblInformation.Parent" xml:space="preserve"> + <value>grbInformation</value> + </data> + <data name=">>lblInformation.ZOrder" xml:space="preserve"> + <value>0</value> + </data> + <data name="grbInformation.Location" type="System.Drawing.Point, System.Drawing"> + <value>12, 12</value> + </data> + <data name="grbInformation.Size" type="System.Drawing.Size, System.Drawing"> + <value>450, 143</value> + </data> + <data name="grbInformation.TabIndex" type="System.Int32, mscorlib"> + <value>26</value> + </data> + <data name="grbInformation.Text" xml:space="preserve"> + <value>Information</value> + </data> + <data name=">>grbInformation.Name" xml:space="preserve"> + <value>grbInformation</value> + </data> + <data name=">>grbInformation.Type" xml:space="preserve"> + <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>grbInformation.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>grbInformation.ZOrder" xml:space="preserve"> + <value>2</value> + </data> + <metadata name="timRefresh.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>17, 17</value> + </metadata> + <data name="btnClose.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms"> + <value>NoControl</value> + </data> + <data name="btnClose.Location" type="System.Drawing.Point, System.Drawing"> + <value>840, 432</value> + </data> + <data name="btnClose.Size" type="System.Drawing.Size, System.Drawing"> + <value>75, 23</value> + </data> + <data name="btnClose.TabIndex" type="System.Int32, mscorlib"> + <value>13</value> + </data> + <data name="btnClose.Text" xml:space="preserve"> + <value>Close</value> + </data> + <data name=">>btnClose.Name" xml:space="preserve"> + <value>btnClose</value> + </data> + <data name=">>btnClose.Type" xml:space="preserve"> + <value>System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>btnClose.Parent" xml:space="preserve"> + <value>$this</value> + </data> + <data name=">>btnClose.ZOrder" xml:space="preserve"> + <value>5</value> + </data> + <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> + <value>True</value> + </metadata> + <data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing"> + <value>6, 13</value> + </data> + <data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing"> + <value>948, 468</value> + </data> + <data name="$this.Text" xml:space="preserve"> + <value>Capture Tool</value> + </data> + <data name=">>timRefresh.Name" xml:space="preserve"> + <value>timRefresh</value> + </data> + <data name=">>timRefresh.Type" xml:space="preserve"> + <value>System.Windows.Forms.Timer, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> + <data name=">>$this.Name" xml:space="preserve"> + <value>ConfigurationForm</value> + </data> + <data name=">>$this.Type" xml:space="preserve"> + <value>System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </data> +</root> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Program.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Program.cs new file mode 100644 index 0000000..c7cb3af --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Program.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +[assembly: CLSCompliant(true)] +namespace TestoCaptureTool +{ + class Program + { + static Communication CalibryListener; + static public TestoDevice _device; + static private string deviceConfigurationFile = System.IO.Path.Combine(Application.CommonAppDataPath, "DeviceSetting.xml"); + + /// <summary> + /// The main entry point for the application. + /// </summary> + [STAThread] + static void Main() + { + //If the file exist, deserializing the configuration saved at the last shutdown. + System.Xml.Serialization.XmlSerializer serializer; + if (System.IO.File.Exists(deviceConfigurationFile)) + { + serializer = new System.Xml.Serialization.XmlSerializer(typeof(TestoDevice)); + System.IO.StreamReader streamReader = new System.IO.StreamReader(deviceConfigurationFile); + try + { + _device = (TestoDevice)serializer.Deserialize(streamReader); + } + catch + { + System.IO.File.Delete(deviceConfigurationFile); + } + finally + { + streamReader.Close(); + streamReader.Dispose(); + } + + if (_device != null) + { + _device.Connect(); + } + } + + //If no configuration was saved or if the deserialization failed, instantiating a new object + if (_device == null) + _device = new TestoDevice(); + + //Starting the process to listen to calibry request + CalibryListener = new Communication(); + //CalibryListener.StartListening(); + + CalibryListener.OpenCommunication(); + + System.Threading.Thread listener = new System.Threading.Thread(new System.Threading.ThreadStart(CalibryListener.StartListening)); + listener.Start(); + + //Displaying the UI to the user + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new ConfigurationForm()); + + CalibryListener.StopListening(); + //On shut down, aborting the listening operation and saving the configuration + listener.Interrupt(); + + serializer = new System.Xml.Serialization.XmlSerializer(typeof(TestoDevice)); + System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(deviceConfigurationFile); + serializer.Serialize(streamWriter, _device); + streamWriter.Close(); + streamWriter.Dispose(); + } + + internal static string AssemblyVersion + { + get + { + return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + } + } + + internal static string AssemblyDescription + { + get + { + // Get all Description attributes on this assembly + object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(System.Reflection.AssemblyDescriptionAttribute), false); + // If there aren't any Description attributes, return an empty string + if (attributes.Length == 0) + return ""; + // If there is a Description attribute, return its value + return ((System.Reflection.AssemblyDescriptionAttribute)attributes[0]).Description; + } + } + + internal static string AssemblyCopyright + { + get + { + // Get all Copyright attributes on this assembly + object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(System.Reflection.AssemblyCopyrightAttribute), false); + // If there aren't any Copyright attributes, return an empty string + if (attributes.Length == 0) + return ""; + // If there is a Copyright attribute, return its value + return ((System.Reflection.AssemblyCopyrightAttribute)attributes[0]).Copyright; + } + } + } + + /// <summary> + /// Class which contains a displayed value and a value used internally + /// </summary> + class CaptionObject + { + private string _caption; + private string _name; + + public CaptionObject(string Caption, string Name) + { + _caption = Caption; + _name = Name; + } + + public string Caption + { + get { return _caption; } + } + + public string Name + { + get { return _name; } + } + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/AssemblyInfo.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e904cf4 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d5c7c598-fe09-4ee1-bcc2-d046cbb55f89")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.Designer.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.Designer.cs new file mode 100644 index 0000000..934eca7 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.832 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace TestoCaptureTool.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestoCaptureTool.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to Channel {0}. + /// </summary> + internal static string channelNumber { + get { + return ResourceManager.GetString("channelNumber", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Device is connected. + /// </summary> + internal static string deviceConnected { + get { + return ResourceManager.GetString("deviceConnected", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Not available on the device. + /// </summary> + internal static string NotAvailable { + get { + return ResourceManager.GetString("NotAvailable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Select a device in the list. + /// </summary> + internal static string selectDevice { + get { + return ResourceManager.GetString("selectDevice", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to COM Port is invalid. Please provide a valid COM Port number.. + /// </summary> + internal static string specifyCOMPort { + get { + return ResourceManager.GetString("specifyCOMPort", resourceCulture); + } + } + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.resx b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.resx new file mode 100644 index 0000000..9796050 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Resources.resx @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="channelNumber" xml:space="preserve"> + <value>Channel {0}</value> + </data> + <data name="deviceConnected" xml:space="preserve"> + <value>Device is connected</value> + </data> + <data name="NotAvailable" xml:space="preserve"> + <value>Not available on the device</value> + </data> + <data name="selectDevice" xml:space="preserve"> + <value>Select a device in the list</value> + </data> + <data name="specifyCOMPort" xml:space="preserve"> + <value>COM Port is invalid. Please provide a valid COM Port number.</value> + </data> +</root> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.Designer.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.Designer.cs new file mode 100644 index 0000000..cddc6ac --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.Designer.cs @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.832 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace TestoCaptureTool.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "8.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("50907")] + public int NetworkPort { + get { + return ((int)(this["NetworkPort"])); + } + } + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.settings b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.settings new file mode 100644 index 0000000..a1ef8c6 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/Settings.settings @@ -0,0 +1,9 @@ +<?xml version='1.0' encoding='utf-8'?> +<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="TestoCaptureTool.Properties" GeneratedClassName="Settings"> + <Profiles /> + <Settings> + <Setting Name="NetworkPort" Type="System.Int32" Scope="Application"> + <Value Profile="(Default)">50907</Value> + </Setting> + </Settings> +</SettingsFile> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/vssver2.scc b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/vssver2.scc new file mode 100644 index 0000000..a31d4f6 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Properties/vssver2.scc differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Settings.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Settings.cs new file mode 100644 index 0000000..3f12b77 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Settings.cs @@ -0,0 +1,28 @@ +namespace TestoCaptureTool.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/Testo435.cs b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Testo435.cs new file mode 100644 index 0000000..85b1953 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/Testo435.cs @@ -0,0 +1,488 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using System.Xml; + +namespace TestoCaptureTool +{ + /// <summary> + /// This class provide the communication functionality with a testo device. + /// </summary> + public class TestoDevice : IDisposable + { + public event EventHandler ConnectionStatusChanged; + + public enum OperationResult + { + Ok = 0, + DeviceTypeNotSpecified, + DeviceTypeNotSupported, + ComPortNotSpecified, + DeviceNotFound, + DeviceNotResponding, + DriverNotInstalled + + } + Tcddka.tcddk _testoDevice; + + short _airTemperatureChannel = -1; + short _waterTemperatureChannel = -1; + short _pressureChannel = -1; + short _humidityChannel = -1; + short _availableChannelCount = -1; + + int _deviceSN; + int _timeOut; + string _deviceType; + short _comPort; + + bool _deviceConnected; + + ~TestoDevice() + { + Dispose(false); + } + + public static string ErrorMessage(OperationResult result) + { + switch(result) + { + case OperationResult.ComPortNotSpecified: return "COM Port not specified"; + case OperationResult.DeviceNotFound: return "Device not found"; + case OperationResult.DeviceNotResponding: return "Device is not responding"; + case OperationResult.DeviceTypeNotSpecified: return "Device type is not specified"; + case OperationResult.DeviceTypeNotSupported: return "Device type is not supported"; + case OperationResult.DriverNotInstalled: return "Driver is not installed"; + default: return result.ToString(); + } + } + + /// <summary> + /// Open a connection with a testo device using a serial port. + /// If the device is an USB device, the correct function will be called. + /// </summary> + /// <param name="COMPort">Port number where the device is connected</param> + /// <returns>Operation result</returns> + public OperationResult Connect(short COMPort) + { + + if (_deviceConnected) + return OperationResult.Ok; + + if (TestoDevice.IsUsbDevice(_deviceType)) + return this.Connect(); + + _comPort = COMPort; + if (_comPort == 0) + throw new InvalidOperationException("Specified COM Port is invalid"); + + try + { + _testoDevice = new Tcddka.tcddk(); + if (_testoDevice.Init(_comPort, _deviceType, _timeOut)) + { + _deviceConnected = true; + if (ConnectionStatusChanged != null) + ConnectionStatusChanged(this, new EventArgs()); + } + } + catch (System.Runtime.InteropServices.COMException) + { + return OperationResult.DeviceNotResponding; + } + + return OperationResult.Ok; + } + + /// <summary> + /// Open a connection with a testo device using an USB connection. + /// If the device uses a serial port, the correct function will be called. + /// </summary> + /// <returns>Operation result</returns> + public OperationResult Connect() + { + + if (String.IsNullOrEmpty(_deviceType)) + return OperationResult.DeviceTypeNotSpecified; + + if (!TestoDevice.IsUsbDevice(_deviceType)) + return this.Connect(_comPort); + + //get a reference to the interface for USB devices + Tcddka.tcusbserials connectedDevices=null; + try + { + connectedDevices = new Tcddka.tcusbserials(); + } + catch + { + if (connectedDevices == null) + return OperationResult.DriverNotInstalled; + } + + //Get a reference to a specific device type (e.g Testo 435) + try + { + connectedDevices.Init(_deviceType); + } + catch (System.Runtime.InteropServices.COMException) + { + _deviceConnected = false; + return OperationResult.DeviceNotFound; + } + + //If a device was found, configure it. + //This version supports only 1 device. The first device found will be used. + if (connectedDevices.Count > 0) + { + System.Collections.IEnumerator testoEnum = (System.Collections.IEnumerator)connectedDevices._NewEnum; + + while (testoEnum.MoveNext()) + { + //Getting the device Serial number + int deviceSN = 0; + + if (Int32.TryParse(testoEnum.Current.ToString(), out deviceSN)) + { + //Get a reference to the device. + _testoDevice = new Tcddka.tcddk(); + try + { + //Initialize the communication between the computer and the device. + _testoDevice.InitSerial(deviceSN, _deviceType, _timeOut); + _availableChannelCount = _testoDevice.NumCols; + + int.TryParse(_testoDevice.Ident, out _deviceSN); + _deviceConnected = true; + + //trying to configure the channel. + //The channel configuration is based on the unit returned by each channel. + //It is assumed that the water temperature is colder than the air temperature. + for (short channelNumber = 0; channelNumber < _availableChannelCount; channelNumber++) + { + string channelUnit = _testoDevice.get_Unit(channelNumber); + if (string.Compare(channelUnit, "C") == 0 || string.Compare(channelUnit, "F") == 0) + { + if (this.AirTemperatureChannel == -1) + { + this.AirTemperatureChannel = channelNumber; + if (this.WaterTemperatureChannel == -1) + { + this.WaterTemperatureChannel = channelNumber; + } + else + { + double temperature1 = _testoDevice.get_RecentVal(this.AirTemperatureChannel); + double temperature2 = _testoDevice.get_RecentVal(this.WaterTemperatureChannel); + if (temperature1 < temperature2) + { + this.AirTemperatureChannel = this.WaterTemperatureChannel; + this.WaterTemperatureChannel = channelNumber; + + } + } + } + else + { + if (this.WaterTemperatureChannel == -1) + { + this.WaterTemperatureChannel = channelNumber; + } + double temperature1 = _testoDevice.get_RecentVal(this.AirTemperatureChannel); + double temperature2 = _testoDevice.get_RecentVal(channelNumber); + if (temperature1 < temperature2) + { + this.WaterTemperatureChannel = this.AirTemperatureChannel; + this.AirTemperatureChannel = channelNumber; + } + else + { + this.WaterTemperatureChannel = channelNumber; + } + } + + } + else if (string.Compare(channelUnit, "hPa") == 0 || string.Compare(channelUnit, "kPa") == 0 || string.Compare(channelUnit, "inW") == 0 || string.Compare(channelUnit, "mbar") == 0 || string.Compare(channelUnit, "Pa") == 0) + { + if (this.PressureChannel == -1) + this.PressureChannel = channelNumber; + } + else if (string.Compare(channelUnit, "%rH") == 0) + { + if (this.HumidityChannel == -1) + this.HumidityChannel = channelNumber; + } + + } + if (ConnectionStatusChanged != null) + ConnectionStatusChanged(this, new EventArgs()); + break; + } + catch (System.Runtime.InteropServices.COMException) + { + return OperationResult.DeviceNotResponding; + } + } + } + } + else + { + return OperationResult.DeviceNotFound; + } + + return OperationResult.Ok; + } + + /// <summary> + /// Close the connection with the device. + /// </summary> + public void Disconnect() + { + if (!_deviceConnected) + return; + + Dispose(true); + _deviceConnected = false; + if (ConnectionStatusChanged != null) + ConnectionStatusChanged(this, new EventArgs()); + } + + public bool IsConnected + { + get { return _deviceConnected; } + } + + static public bool IsUsbDevice (string deviceType) + { + if (string.Compare(deviceType, "testo435-635-735", true, System.Globalization.CultureInfo.InvariantCulture) == 0) + return true; + + return false; + } + + public short ComPort + { + get { return _comPort; } + set { _comPort = value; } + } + + public string DeviceType + { + get { return _deviceType; } + set + { + if (_deviceConnected) + Disconnect(); + + _deviceType = value; + switch (_deviceType) + { + case "testostor175": + _timeOut = 7000; + _deviceType = DeviceType; + break; + case "testostor171": + _timeOut = 7000; + _deviceType = DeviceType; + break; + case "testo400-650-950": + _timeOut = 30000; + _deviceType = DeviceType; + break; + case "testo445-645-945-946-545": + _timeOut = 7000; + _deviceType = DeviceType; + break; + case "testo454-2000": + _timeOut = 38000; + _deviceType = DeviceType; + break; + case "testo175-177": + _timeOut = 7000; + _deviceType = DeviceType; + break; + case "testo174": + _timeOut = 12000; + _deviceType = DeviceType; + break; + case "testo300-M-XL": + _timeOut = 7000; + _deviceType = DeviceType; + break; + case "testo435-635-735": + _deviceType = DeviceType; + _timeOut = 7000; + break; + default: + _deviceType = ""; + break; + } + } + + } + + public int AvailableChannelCount + { + get { return _availableChannelCount; } + } + + /// <summary> + /// Retrieve the Air temperature. The value is in C. + /// If the device provides the temperature in F, it will be converted to C + /// </summary> + public double AirTemperature + { + get + { + if (!_deviceConnected) + return 0; + + _testoDevice.Get(); + if (_availableChannelCount > _airTemperatureChannel && _airTemperatureChannel > -1) + { + double temperature = _testoDevice.get_RecentVal(_airTemperatureChannel); + string unit = _testoDevice.get_Unit(_airTemperatureChannel); + if (string.Compare(unit, "C") == 0) + return temperature; + else if (string.Compare(unit, "F") == 0) + return (temperature-32)/1.8; + else + return temperature; + } + + return 0; + } + } + + /// <summary> + /// Retrieve the Water temperature. The value is in C. + /// If the device provides the temperature in F, it will be converted to C + /// </summary> + public double WaterTemperature + { + get + { + if (!_deviceConnected) + return 0; + + + if (_availableChannelCount > _waterTemperatureChannel && _waterTemperatureChannel > -1) + { + double temperature = _testoDevice.get_RecentVal(_waterTemperatureChannel); + string unit = _testoDevice.get_Unit(_waterTemperatureChannel); + if (string.Compare(unit, "C") == 0) + return temperature; + else if (string.Compare(unit, "F") == 0) + return (temperature - 32) / 1.8; + else + return temperature; + } + + return 0; + } + } + + /// <summary> + /// Retrieve the Pressure. The value is in kPa. + /// If the device provides the temperature in hPa, mbar or Pa, it will be converted to kPa + /// </summary> + public double Pressure + { + get + { + if (!_deviceConnected) + return 0; + + + if (_availableChannelCount > _pressureChannel & _pressureChannel > -1) + { + double pressure = _testoDevice.get_RecentVal(_pressureChannel); + string unit = _testoDevice.get_Unit(_pressureChannel); + if (string.Compare(unit, "hPa") == 0) + return pressure / 10; + else if (string.Compare(unit, "kPa") == 0) + return pressure; + else if (string.Compare(unit, "inW") == 0) + return pressure / (10 / 1013); + else if (string.Compare(unit, "mbar") == 0) + return pressure / 10; + else if (string.Compare(unit, "Pa") == 0) + return pressure / 1000; + else + return pressure; + } + return 0; + } + } + + /// <summary> + /// Retrieve the Humidity. The value is in %rH. + /// </summary> + public double Humidity + { + get + { + if (!_deviceConnected) + return 0; + + + if (_availableChannelCount > _humidityChannel && _humidityChannel > -1) + return _testoDevice.get_RecentVal(_humidityChannel); + return 0; + } + } + + public int DeviceSN + { + get { return _deviceSN; } + } + + public short AirTemperatureChannel + { + get { return _airTemperatureChannel; } + set { _airTemperatureChannel = value; } + } + + public short WaterTemperatureChannel + { + get { return _waterTemperatureChannel; } + set { _waterTemperatureChannel = value; } + } + + public short PressureChannel + { + get { return _pressureChannel; } + set { _pressureChannel = value; } + } + + public short HumidityChannel + { + get { return _humidityChannel; } + set { _humidityChannel = value; } + } + + #region IDisposable Members + + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + + } + if (_testoDevice != null) + System.Runtime.InteropServices.Marshal.ReleaseComObject(_testoDevice); + + _testoDevice = null; + GC.SuppressFinalize(this); + } + + #endregion + + } +} diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/TestoCapture.csproj b/Calibry/5.0/PS_Deployment/Files/capture/Source code/TestoCapture.csproj new file mode 100644 index 0000000..6540bba --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/TestoCapture.csproj @@ -0,0 +1,140 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>8.0.50727</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{AA47E4A9-0625-49FB-ABC2-2E90FB34B471}</ProjectGuid> + <OutputType>WinExe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>TestoCaptureTool</RootNamespace> + <AssemblyName>TestoCapture</AssemblyName> + <StartupObject>TestoCaptureTool.Program</StartupObject> + <SccProjectName> + </SccProjectName> + <SccLocalPath> + </SccLocalPath> + <SccAuxPath> + </SccAuxPath> + <SccProvider> + </SccProvider> + <ManifestCertificateThumbprint>5B4B94893678DB9BDE24EC0F0BD2AFC06B3DE386</ManifestCertificateThumbprint> + <ManifestKeyFile>TestoCapture_TemporaryKey.pfx</ManifestKeyFile> + <GenerateManifests>false</GenerateManifests> + <TargetZone>LocalIntranet</TargetZone> + <SignManifests>false</SignManifests> + <IsWebBootstrapper>false</IsWebBootstrapper> + <PublishUrl>publish\</PublishUrl> + <Install>true</Install> + <InstallFrom>Disk</InstallFrom> + <UpdateEnabled>false</UpdateEnabled> + <UpdateMode>Foreground</UpdateMode> + <UpdateInterval>7</UpdateInterval> + <UpdateIntervalUnits>Days</UpdateIntervalUnits> + <UpdatePeriodically>false</UpdatePeriodically> + <UpdateRequired>false</UpdateRequired> + <MapFileExtensions>true</MapFileExtensions> + <ApplicationVersion>1.0.0.1</ApplicationVersion> + <BootstrapperEnabled>true</BootstrapperEnabled> + <ApplicationIcon> + </ApplicationIcon> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <RunCodeAnalysis>false</RunCodeAnalysis> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>none</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Data" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Windows.Forms" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Communication.cs" /> + <Compile Include="ConfigurationForm.cs"> + <SubType>Form</SubType> + </Compile> + <Compile Include="ConfigurationForm.Designer.cs"> + <DependentUpon>ConfigurationForm.cs</DependentUpon> + </Compile> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Properties\Settings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTimeSharedInput>True</DesignTimeSharedInput> + <DependentUpon>Settings.settings</DependentUpon> + </Compile> + <Compile Include="Settings.cs" /> + <Compile Include="Testo435.cs" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="ConfigurationForm.resx"> + <SubType>Designer</SubType> + <DependentUpon>ConfigurationForm.cs</DependentUpon> + </EmbeddedResource> + <EmbeddedResource Include="Properties\Resources.resx"> + <SubType>Designer</SubType> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <COMReference Include="Tcddka"> + <Guid>{933CB583-B37E-11D2-B9E5-0000C0A215C3}</Guid> + <VersionMajor>1</VersionMajor> + <VersionMinor>2</VersionMinor> + <Lcid>0</Lcid> + <WrapperTool>tlbimp</WrapperTool> + <Isolated>False</Isolated> + </COMReference> + </ItemGroup> + <ItemGroup> + <None Include="Properties\Settings.settings"> + <Generator>SettingsSingleFileGenerator</Generator> + <LastGenOutput>Settings.Designer.cs</LastGenOutput> + </None> + </ItemGroup> + <ItemGroup> + <None Include="app.config" /> + <None Include="Resources\capture_header_image.png" /> + </ItemGroup> + <ItemGroup> + <BootstrapperPackage Include="Microsoft.Net.Framework.2.0"> + <Visible>False</Visible> + <ProductName>.NET Framework 2.0</ProductName> + <Install>true</Install> + </BootstrapperPackage> + </ItemGroup> + <ItemGroup> + <Folder Include="Properties\DataSources\" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/Source code/app.config b/Calibry/5.0/PS_Deployment/Files/capture/Source code/app.config new file mode 100644 index 0000000..222d8c7 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/Source code/app.config @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <configSections> + <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > + <section name="TestoCaptureTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> + </sectionGroup> + </configSections> + <applicationSettings> + <TestoCaptureTool.Properties.Settings> + <setting name="NetworkPort" serializeAs="String"> + <value>50907</value> + </setting> + </TestoCaptureTool.Properties.Settings> + </applicationSettings> +</configuration> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/WindowsInstaller-KB893803-x86.exe b/Calibry/5.0/PS_Deployment/Files/capture/WindowsInstaller-KB893803-x86.exe new file mode 100644 index 0000000..2a214ed Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/WindowsInstaller-KB893803-x86.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/dotnetfx20.exe b/Calibry/5.0/PS_Deployment/Files/capture/dotnetfx20.exe new file mode 100644 index 0000000..a121c6f Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/dotnetfx20.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/isnetfx.exe b/Calibry/5.0/PS_Deployment/Files/capture/isnetfx.exe new file mode 100644 index 0000000..8115ff6 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/isnetfx.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/Interop.Tcddka.dll b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/Interop.Tcddka.dll new file mode 100644 index 0000000..a799da3 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/Interop.Tcddka.dll differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe new file mode 100644 index 0000000..2d7ff3e Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe.config b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe.config new file mode 100644 index 0000000..222d8c7 --- /dev/null +++ b/Calibry/5.0/PS_Deployment/Files/capture/program files/Mettler-Toledo/Calibry environmental conditions capture/TestoCapture.exe.config @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <configSections> + <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > + <section name="TestoCaptureTool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> + </sectionGroup> + </configSections> + <applicationSettings> + <TestoCaptureTool.Properties.Settings> + <setting name="NetworkPort" serializeAs="String"> + <value>50907</value> + </setting> + </TestoCaptureTool.Properties.Settings> + </applicationSettings> +</configuration> \ No newline at end of file diff --git a/Calibry/5.0/PS_Deployment/Files/capture/setup.exe b/Calibry/5.0/PS_Deployment/Files/capture/setup.exe new file mode 100644 index 0000000..9199ee0 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/capture/setup.exe differ diff --git a/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_d.pdf b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_d.pdf new file mode 100644 index 0000000..848a7da Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_d.pdf differ diff --git a/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_e.pdf b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_e.pdf new file mode 100644 index 0000000..4cfb1cb Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_e.pdf differ diff --git a/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_f.pdf b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_f.pdf new file mode 100644 index 0000000..2d3f67d Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_f.pdf differ diff --git a/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_i.pdf b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_i.pdf new file mode 100644 index 0000000..8a799e2 Binary files /dev/null and b/Calibry/5.0/PS_Deployment/Files/pdf/calibry_manual_i.pdf differ