From fb46337f985a35477107a0729df1d68039f76db1 Mon Sep 17 00:00:00 2001 From: Mikael Karlsson <43226266+Micke-K@users.noreply.github.com> Date: Mon, 12 Jul 2021 20:24:27 +1000 Subject: [PATCH] 3.1.7 --- Core.psm1 | 13 +- Documentation/Strings-cs.json | Bin 2383436 -> 2397440 bytes Documentation/Strings-de.json | Bin 2527304 -> 2542156 bytes Documentation/Strings-en.json | Bin 2263136 -> 2277060 bytes Documentation/Strings-es.json | Bin 2540694 -> 2555466 bytes Documentation/Strings-fr.json | Bin 2544632 -> 2559176 bytes Documentation/Strings-hu.json | Bin 2504608 -> 2518624 bytes Documentation/Strings-it.json | Bin 2540114 -> 2554880 bytes Documentation/Strings-ja.json | Bin 1887392 -> 1899916 bytes Documentation/Strings-ko.json | Bin 1856500 -> 1868666 bytes Documentation/Strings-nl.json | Bin 2474902 -> 2489160 bytes Documentation/Strings-pl.json | Bin 2499582 -> 2514094 bytes Documentation/Strings-pt.json | Bin 2263136 -> 2277060 bytes Documentation/Strings-ru.json | Bin 2455886 -> 2470556 bytes Documentation/Strings-sv.json | Bin 2331442 -> 2345538 bytes Documentation/Strings-tr.json | Bin 2379232 -> 2393258 bytes Documentation/Strings-zh-chs.json | Bin 2263136 -> 2277060 bytes Documentation/Strings-zh-cht.json | Bin 2263136 -> 2277060 bytes Documentation/Strings-zh-hans.json | Bin 1686844 -> 1698534 bytes Documentation/Strings-zh-hant.json | Bin 1705536 -> 1717270 bytes Documentation/Strings-zh.json | Bin 2263136 -> 2277060 bytes Extensions/Compare.psm1 | 18 +- Extensions/Copy.psm1 | 10 +- Extensions/Documentation.psm1 | 10 +- Extensions/DocumentationCustom.psm1 | 173 +++++++++- Extensions/EndpointManager.psm1 | 210 ++++++++++-- Extensions/MSGraph.psm1 | 496 ++++++++++++++++++++++------ README.md | 47 ++- ReleaseNotes.md | 23 ++ Xaml/BulkImportForm.xaml | 19 +- Xaml/ImportForm.xaml | 36 +- 31 files changed, 890 insertions(+), 165 deletions(-) diff --git a/Core.psm1 b/Core.psm1 index b512a53..ca6c3ec 100644 --- a/Core.psm1 +++ b/Core.psm1 @@ -12,7 +12,7 @@ This module handles the WPF UI function Get-ModuleVersion { - '3.1.3' + '3.1.4' } function Start-CoreApp @@ -914,14 +914,17 @@ function Add-SettingCheckBox function Add-SettingComboBox { - param($id, $value, $itemsData) + param($id, $value, $settingObj) + + $nameProp = ?? $settingObj.DisplayMemberPath "Name" + $valueProp = ?? $settingObj.SelectedValuePath "Value" $xaml = @" - + "@ $xamlObj = [Windows.Markup.XamlReader]::Parse($xaml) - $xamlObj.ItemsSource = $itemsData + $xamlObj.ItemsSource = $settingObj.ItemsSource if($value) { $xamlObj.SelectedValue = $value @@ -979,7 +982,7 @@ function Add-SettingValue } elseif($settingValue.Type -eq "List") { - $settingObj = Add-SettingComboBox $id $value $settingValue.ItemsSource + $settingObj = Add-SettingComboBox $id $value $settingValue } else { diff --git a/Documentation/Strings-cs.json b/Documentation/Strings-cs.json index 2a50d9c67a0ff099bc72b30bb1a7d817a37d67ce..48352b00b619e66e13a390aeb014815b4307b25c 100644 GIT binary patch delta 3565 zcmb7Ge{2)?75|=n{*lYszW8!+9Q$$?Ck%=P4J_-{Rx<&T4AMk3g`zGJ2PL{nMTm1C*(((Q2xIl0a zoCJ~JBDe`2f|pQ4kO;*DncyS%2?2saC?Nz1Dxs915y}W5LOCH!s31fLQG!mWJeiIg zN3*zmt-2ibX^;3qSrA_yb2TBQ*HqkoB4K82iHPeroU*V3zk1qO17*^&4&zSyWr&bYl3NM+^+GKDLD{GGvitDQq(xA^faQkJ~oaY9tY7oRSzx zu`afQu$BzBu{0g)W}69JtP{dQ-4J%6%P zyihNdv$M~bPr&$Zym^Ox8P-qs+1sgYxZe@RvkP@8j{}dP?V=<)*j5%}4>6N|F(wOL znCjD)StLeg#4u4KFFOj18rU=u&NtHsC|IGVRg^*>SB7ZbF@<^FAqG+;99MyReVjdl zC5!cH9A2s``1MY`+1Bq14Aa8oTq>ZB}ID=qK*0+L7`4{A{A^ir5l=pqi+&E7aaL&DFVX<`|SOgL5a9S=(O zfuUZ#!OncoMZ@jMP&rW6xcGn`#+Ob>ar^c+E^@eCUF4@14{xA1@2Jiu)ZV13yx?|- zYCBzf7u%V~brnW7>RRq@=fz0Z0ZKiuJoC(3DQG}Q;&`9F+|H){wScH+=`p{QLLFC= ziu62B#maw0+{J0j%&KclohLzZg+Fvz5cPg5$wF6;&_(god-9x(zgZ&cyif=4irhlx zS@z^y<8FG$*>T@ejCcX5Wq>LM-38n1mr*8?V7z*dm=Uef>X#I5R2 z21YiCNDRj$wXo6?WR_2!Ox%$Sr7%Da2gogN>=jlc?xFyFShB(q!I`-3$GW)QWXsDB z8FLSD&$G|Cz_SP2>@!1cnpo9u7sy>aAEVIQ2>BJu4}=FOpB-Oho~Po4eJ(eRHa^&R z@d=H3Bu}vuwNA09k8&HNx4oEdbf_rXPIPk9kDCN+6JG1K8!`;x+3f`*h8u?LO0j#r zmBjis9q3^Fm|CsR!+5K{!sg3uwS3#PG%1s0OB}gnVwE05ON;(M&Rpl$N+XdN=TERr z!XUd*FmsgMAUbWt#~iC5$7&R`eNvsm%rU2k4}Wf&o<0gO$ofZ1sAh!+PB%ByUXYm(DjyqcPZ2Pc70@|UQJ=HGe!o-9=heJO0G);(10c1igB zNU0ZxP6re$HmVdv>uO~5+>#WH_dfr!m@Fyq{eV-YBCU(47{4hs%jnPhj6L#KD<3C2a#G|@_9Ceo`4kIumgt{YIsX%^y*sAC@V8B$`D3*r&96w44|Go)jfV z?SP4l7Q)!4#X)B$QC-p|Xp07kflrvGqTMWT$V!# zA^Kxg!eh5wLEL;vXyL5*mjE*_RJ+wi>zZivQ%YTw0<9=8dOTX*p{46eEB+f(tUqP? zvFSU88+C0$&{x2h7OzSb7jC-5Vw>ew3U^PI)R~G{#D%gisbrZE!kL8MC*@Uubw{-g zMTKlHE{KKj`3Lm-*H z?rpR$`Zu=>h3i-UNvs9KC1N>vG>tDpX)2f5Wq}t*C(bt3%*P}jVL5(Qa_4;E zAoeV*A)WAEV@+g^(dbZ#7_z@ud|7EAXPYSi->kD(bKS3D<9DM}@R#)>?<_f7)ch;$ zf*L>KoZam6_IR#bbdK6Cz7Oxjv@9D){JToVFMG8zd~ixD$I$MWh<(3xDY()|EnZt! zVOKWHutUbJUlf^0e~oUA#!c}5>#%!OT;PA~EsQU7c`>}a#>CaVUN^3G`n_7=N1=-? z;wu8C;cmRG36&Kx*YpaDa_6W)eP91Wv3BeYXB0y(8sfYHl^LJ#9i%yLv)q(##>4^o ZG8|oG#!*}CEZ^V!VD-u72WuSj{sU5R2TlM0 delta 182 zcmWm1I|{-u0DxhV)>`YczTZ~u0eXU*DToIU1T#1(Vvis=Wy_#Ab@WWd-{Bj+!}BV> z?~AZIm499$yq|->*usO43jVJmL=6$@XrPG}+UOug7d`Ybzz`#hF~Jlw%(1`{D9>Dbf$>@ z@Q-PpCOzNte((K#zrH(il)rZPb$)cB=HRK5F+!YBa`x28#Fe)(u}l$>TBtXFd9vo< zWDQTy5%dIsU?7MDBf&%{BA5vlf<&+qYy>+&CO8OAf{WlLcnDsCkKiX169R-FAw&oh zA_RpHJv&(wJJpKj^QC^gcHJl-`qL68PT%Hhu{={2nEqyld7w;2VrN3e!w^4=o;M7l zG0C-Zom?AYzl#J>_sG2hV>#VcjNUW}_I@tKZRe6)h8RgMjSm8fg4Ex{DNc2NiRkL& z(p(4EOK2q4F0O}5L3t>6PkhQL@a;p5KWTJ3r!s^mpC5h=;F6RnTXP2?n9 z^X#oi31ms}!)$i%6HSs( zL^7nLmrmafDaB8m8vE&9Cdp4kX64U@zhV{aX$nS46XiB@FQDdbM8Uc$rInf1{uD~1 zMyYTs%cm(yFDYOisv+g#Too6kpE$RgOzI``4*D-ZKu)Oql=QpHrDEO&)W=~fM4 zMba>vN_V=vhpNwuMSWa$O2O4RN-$qRbSOAjCGOz*RV7;SE89p(-qqK5MYLqN0djOZ zy9P&iC1#Csv$>U&-YyE;AK}sVg+F;#$;XWK)6#@{DPQ@jW-Zf-yzWOWFoInp zunY?mszpJq zp)n^diVfp-Ww91IMz^qZu`$EaaHlcoLCT|;3-nh%@QeB+*NH!M>l!pU`Xq$=DZX8r zTtOdh;{L(D=lgJ|hZoAb)XcFCRh=#1Z>BrgpwrHJ)j3n?h#$hpY<>-F@07|2-78H` zRm69xEY?NhfuVKly%b_5)P>|++kD(OY;t1!&A5PfLpCpdaLM4rXqNuGWU#yO$zZ}i zR9Dli-CF&O#q45<-$PS@%^lWRZCoGj802C{E!E5i9GjL@XY{)Y_RG$4-r_StaP zY9Myg62BUQ-ry{>)L8^nI88E6i$OE)HCZVJuO}od zSS~toW4=kk=gOs!y_YMeSz1n&!vdYcH}4AdsHUZW@k4qugjWPV-rpWJVf>sV;g1N| zkULzwaUb~|of^EAyc z1kNqKLUR|rtZ!zE2K!&MnXuu7@FqrQGab3BcEPxdws1e!iewFKb9vk!vd;(x*9%99 z!>2`X=MJNU(I3Xw(C{Jq$6UO?Q(2r4E0Me86!4=ZQYc?)G#=*Bp4!aq!huxKhqsnU zQOuh(ipazxR3aN)0*<{Wdy!b=jbP~LK0J-^kQLPU?i;MdWs|Q=07+dR<44rHk;O0I#_~g5>V!uH^^E-kGlMX?|(&aQF zww%*D@vUyV7e8Ji1z54j7`fn3n<;w1B@PdinNj_dXaLcDg821cQPX8<*V{7mWqo68 zE}~yFnegVZ64AhRPX`@jSaCJw~|P8`2nYQ}{fQk3t-?mdPk;)E!lie4qdPHVdRLg25jRG|HAG5&wu8Iu+MHq#2Spz{^;$nBRIZ+gf@y+gkQs`M&^ghD-4P delta 166 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 delta 192 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 Z1b_4QLb}?M2H)PnV})l2kA?Rv{~y|jA(a3C delta 176 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 CZy{U& diff --git a/Documentation/Strings-ko.json b/Documentation/Strings-ko.json index 682dfc2ce62cfb2c1734e65feb9167b3444a99d5..ae3b73345e28804d60d16e0f4e69280077a2fe6f 100644 GIT binary patch delta 3234 zcmcImeQaCR6~DHxUmV9tY{&1#$xGmocG_U=NQ8#kiI1!#DMc-`X}U!;t>S#N33c<; zX9`tHgEm2j_()^esU}n$hy)Cmrmi7&aEp`GaRSkBTBQioPVNQ>&^9tDQoCss&V4Vb z-!tMrP;{c`<(zZR`Tc(9+`IT{@Bg0Y8-2Oz$&N#Aw34xoL#2xse;25FGEikeGoqQ$ zD4H3KL(4(4pyi@j(elu2X!&RbXoYBYGzXd!tq9G9#-j;n#bbdg_tYl%(=8=#7)+Zf z4B8z|fAEDmIP-6J>Ge-mSa~0H!m&!VLR#Zt|(=s5hl?%xxn+ zJPDJ*OD_sMyzDD^kRenJ_Ekojw;N$nb8Lg*VPO+Pxos$y)BWSh@;|^H5xARV(v3)E z!9Ie(V+D z4y8B=_ z)#JhF$fU;2Kd_SKv|ze+vL8;K6^b$#uINB;yS_(fm*$zyG>M=&WT5391#!J3<>YiK zOOiJ}gvIwL4XB#TNWDLKHKs``?m<3Sb^4r5^C;Tu15xdlo}lG2dH6fL^^Zl))tu3@ zHpu1ELK$iBDM=NoVfcNaIMcd6aD}n3AJ5&b=7g$03nk{om^VfI!`50xMFXV2Dx{G9 zg6DWW{fpcfOSOv?swoib7f@BlC5ydi8=6`&-IUn=B8t?y<91~nc* zhW0mvZA*%2v(x#Mz@$RdBK?;);!=^f)u;70j85IeVa3NFbIyPz#qGlxJnAQ@aU6AHly{NjEsc z*i-4)(wq$am-4E9RL`x7nsCRde%C?*2bQ;m+ZBk=xtvjn%PVW4j7)w$C7;p=2~z8w z)a1b}_v@tLo+U!=b5i7@4_nTTubdmvt|NyJeb}00%T}er^C9Z0<&3+<-|SoE51hRh zjy)}MAiX47;mmttm#QJD*F<4F?Jivv9({ePCMl7t?*yX~S&XfW$m0^}pE~_sBYxeO zh~d|U@r#r3nf|EX-r-eJE}W!R2ZQZVTrF&U$yh@xRDCTJmAhh}hRB-|S&bLTLgL`q z(OHS`(~B{Q{PTPVZ0Rs<02aPh4d002yU{Z9*f8FY2d^0qMwhcZeIXc^NK+&o%*uhd zb-|GuivTyipJx`eC#x}X^7mN2_FM{;xA9g(Ef^ki{}k+vg`A{zhX>5r=HHDnEM}vh zgnNQVMI?Q0JolPZR=WBS$Tt5iTwexBb zCd>p|o-%G2%EcSsZ?%9uOkFV0&fmpKW1AfoPUKPZUc7>RaCx;@496aIaZVLt)@3CU zWq^8XJ zANe-ed^^7md-A7`P&1negm@^IhyRBQES!oUq|Wghms~`}h14pH$SBgS*pL^EadV=c z@^CF&z^zsT83|K~w~$sYB4$n3iO(Tod4iehX=D0SED48u78W8sC1Eb-DC@{c+XUF})DP z-;MK3i(OK-!mZ0Y zPt|(k{F;g9r+#RyLmD?d6x32KhiR4F0+$ccV)$|`wL)-|V|rFJ0HLo0!PFEv3Ufo^ z29b@(K2n1)@4_JVHkS!oq^R`g|g;f6(q(+_NuWSM@hg@a?d)iFt>=>@6m9+MY5W@#7VX9i*r%?iY9K+F!r z96-zo#9Tnk4a7V^%nQVPK+F%s0zfPX#6mzU48$TpEDFS8Kr9Z#5_iFEC`>zQB;_+nwnJmzmkNzvz~p^c?`4;w`KI diff --git a/Documentation/Strings-nl.json b/Documentation/Strings-nl.json index 6254fcf10539dfbf9a656ff6757498af3ae53604..99f475f83c4c49d54c475eaab36e732e0c5e7e12 100644 GIT binary patch delta 3506 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 RyQc7tM~I$&gxKMy{{w|GAo>6R delta 196 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 delta 186 zcmWm4OA3NO0Dxg8(;oKHEPH2W?WDciIa-T>E?|Ms4Wix9UTEkPf%}dS{Vl%TcY9pK z_jwlgm&{L@+fij@p$;1k8fc=0HeCE)7d`YbfCnE#1Q=nA5ED!>!yF4NvBDY=Hi)s+ eWtGI<(MV;aKusHIeXjXiB#tW5oi5V7fA|6A6Je?V diff --git a/Documentation/Strings-pt.json b/Documentation/Strings-pt.json index 9efa1b40becfa48bae1885704d58305d7b0d80df..bfc02a543f217e8f28dff2bbc8ce8dec5cb7b402 100644 GIT binary patch delta 3269 zcmbVOYiv{J8UBuae0;wiJHDLQK6VjeEp%jE>Dr}($Y|MFd<+`N5zj02ovhMEmPA@jaHyq(6)|sknR1>aV}(& zwocab!{@zx@8x;l@0-l;6VBe+Cybn0z4s|^j1VWNC!X>qYU;mpa`oPms|A9AU?hkH z6G0-F2^NBtU?bQG4uX^5BDe`2f|uYU$OJzjKqw&u2_Zt55Fsdp(i10FM~_~{L_QXR z8ZwI*@;iOF`J7<`Vn!h{_sxcMCh0NvusYVrnsDr-@Z#VBMKaSL*1?)_X}=Q0swbp! zjP@D6iIH1I(Vb)4SsST$lSU&;gO4K?h!0995O}cAtE6>~3k^4s0!a9(+Y7gSILy8aCx0DiWy{4?<7A*zUB09TS z7pY~7kz7BlC|G)4*w1C=J#=6*YsZ?4N&u_=D(=F_Vfzv*g-q%_BzWT@B`01mZs7iE zQBiLc%{g+$jmlmoqo4KoiiwXaPRZV;WkQzhaR|f8PAv>@k#m8FZNW<~D^aM!iVGLc zDV4g~WUE7T_K^23?R;LiGN&P|7p~%OPRW{{3+&6kQo;)Y8#-bT1KpHOnuo^L@v3XZ zX+-oWWxVb6*+aM{)nrz41M9}6oKeQIqe`tF#&o?)w5F)IJGd8=o$v;=ih&EXLsG1h zCD{W^rC*XgOfJ0T>iyih9iDpyG2BGfDb~9{sfzy7SA6)sdY`7~ZD%=(aUn}q$cCUm z-8?V6fYE71v}Y**&I}&Lz?+mXZyXu|2JwzYA`Q<`r_p>ckI>hm1Y_3>8gr;`X9UsO zr3J-(Ie$l8)qJTp+@hVc67fL!QFfmo9HX|p|v2CI(^gy^^+{ja}C4r=Lljj^6=87p)YHZ#^j<%!m z+mejF_4s1-8Y0Q;H!6~DGJRaxs`taDZKr(l0bKVFc5s)!`TCr%^lSnjgry!nYjp+Xpxz7IHs!swJxpfg|nO?Sk zeoTDlCFjzh-XU$o*rQZ{U8L2_x^Vq%(i%Nm71|BNky(Q-k= z=P8c^XQyO8ruMj8&J1}?Y1E@(LD?_ea>3u+@B^U=o&lpx=A_$Av&`24ABAR?#j$s- z!DuUe>Y`74NL99iu#r@nX;yNyKNB}-FHOhobps7H&5=`gR82N*RAkLXI~nuk`JY`} z|12CZ@fNnB|E}03&5Myw&h-zZ08YJYb^MnYxC2~HMQiw)9lRpgOzX8xEqo(XM!e)< z>y667J7c4s7pvpKflShcSHk8>ihkA@Q}0AWICC`O!b4-`YMvMEN~thz6|Eb!`-6&u zfkHG;xM{WEP0{YdD~IF|G9k0Xi(Yhl=gxQ%%Vy#pJn&51MG19dZLK4KXRBlj2IZ&+ zpC2x*n)8j1suB@Ylo>5J{%OcXd7I1m*%kLhBzQ_p5=H|i8w}6ORs1vGteC#bC7O58 zy_Tc9a^Pb%j*2US=+#r+K^5ifY-PLkv{%07e1x0!y&tm)SqPED9rJAQ@>Ve_yMg!4PP7fl|+}*M}BwmR`T0a)wsl`I?GgixK;>ba+k@1Q=5GjjC@~m z;B8(S&+DZUhT0t#r2ia{&~VkU6#M(!H9Qi&cZFVfA5>%<%$8U%emrDBO@-vLK0zlm z(K#LX`)BG}d|Cg(Rkt5WXZ)IsI{BOC=1!vORynyb-YNTry*S9H18YNY5OQt->K1sB_*VQe@Tbs=w~)zdH|OQ>t|__aO= z%kk+%JdPzLsvnn6h5RO!TDTkic6t-c^hRvR%Mk}MKQYoP@5y8hrdz!t%CC8C#uQtJ z%|`+Oy!SmfEx3Bocl$JLo!2#|7~d_u=s%YT;>OV8a%_eALPQGc_YJ@DAEL-xiL?Bf zm__3@a{!sPmr+v)-|Cy6LB2d}P}j+#@WrR^3p0}6 zM^65t$ud4J{H_}7qo>NGvs6UidaJbP#@JbqH}Rn*B0Er5U0Q9_@z9|_gkAm86} td#SZHHIrXHe)DgpjdY@VyJn&PD{GLy$Kb=r3PIXCnogV;O(%!K{{oX{=5+u7 delta 192 zcmWl~y9$C(0Dxi4OJ!De&u(gyW3+ajprN5X=v^F?CZpkiM6|d@n;;DhJwgxAiv;}* zAK&YHP(H~)xj%%wu3b%Dgkj3t#`ol6Wr*Y6IwheN#i@$_It-LiK@}!yuuw+>P1tCm wjSjl#p^pKEa4^Cc6HGCKo5!iQe20aI3ZbBs7ed|cAtghGeR$?je#&lMwtd(T=tIqifTtE9*AVfZDB8v?EOIsYSQ6Y{75V=5)1?* zfe}muiC`vJ2v&kjuo3J82f;~j5!{44f`{NGC`yZOw7t}A?iDr*N#RkvRUR3~*rfi=%$S5cjH>EE`akF4Xn7A{B-MK@!;md zTFBxQR*XuR9h$q=C0SEDoUmu7_oxGVf_5%nXrh}gaHwUTF__A?B8Y>8q3+5tl{Glu8aidamEM9WzRX6@ntxWndWAy$N`dV z7rJqICGllT><)#wteP#miI!H9WK$W*`>Hi3&Q;wWT5xe2Zxdfgyc^q-ISCAWQNm1} z+yMUZu%S6qGybEkpQ^k2w$emLj#B!y0_NJScZ@I4Oio94P)=*!4<8zAvtvpT*A9Os zGBmzeVn^(HNeb=-AqmINMxtoFCff0WY*sLN$j7`DEG(>~SA#S?i{H;nR+Wx0;`^D){9eA)C-0`2to-0Xx{6M*V^h?y(5!Ei1VuAz14|mr4ZPIcF+7zby$arS z%1A#Mv|!7(N20ld1=$QfAoMPUffKc{#w zx>NM4m24}KEiBYioma4B#NL&}+U{vvK>vWotL}LYAAMX#;4G|!s5%!9O*$DK_k}IE zw%_hVfv32Nv!fu=MXR)l4>6709El@zGuM;l8nXSJ!-hiqq?!3~{WDoY{ii|aiGv}% zS$v6OG*l`fF=19*^X}1mq=KPFUZ+`Eb+z8zKANFTv>x^lXo4`_Yx3UHf{U|f6(yRu zyE=K5Yyp*mQ@%!jDOF=;u+WQN9g9eKey5m^H%3$q`=)Km*D4Ou-=O@)LZ!?!+gTru zUohLhlIA~%2NOA2kZuz$FWF>IR*dGOK6sMC=}cX~$hi4?2x@=R34 zK$14)jZb%@B}ng^=&;>!qw*(Fns~z( zAb+{?$6^PrZZDmW#&;YA7!BC<#AY>2e1Yx9Tc=BWITK9b_|Wu@BY#$<{-{?{GiQ-M zZ|*0{JIM<@t}%XAp$GLxVNZ^UnWf=&{xU%QHYNXFrTgD?=SSMe*!)U1Y3`*~a2=^Zq4sKkrOVY*AP!zD4) TUlM~yZnl)1x!F?Mdf$Hl&@2m9 delta 181 zcmWN=xedZF06|D^Z$ zrXv5{M0PM%6&@wa^jTq*HP+c+lP$K{VV6DjIpB~Zjyd6!GtL<>6`t|V&aTJ1KfQQ=3`8Wf69tExm*Kx%P0cgBxd zJNXA%eb)NkbI$$F^WMFD{(1h|iR1j4n;?gOLomps$xjvI=OzXi;Huc zxhQ?&oW^Acw2DjhwnirTBsdRFtJ(F6&DGBTQ$bOnjaalq; zeIMpV$xuJnLr8JmCNqP0;)v>X_HcKILW-0Ixgm^QFWJUOd1xHE43f#komGBR9Uw|wT%XB)jvLU|28&|# ziH8@w%xXWmY!>gv&+BcE8zh)eVw?yRTV!$SCS=HuTV$f!L}G5*DF%D>&@sE;;95*n zQsAFc-55Bo>bdR48tL}6(jOdGw=y~;{S~Va%Mu+H#MRklsmrl-6Pd!4Ec(-Tagwr% zi}kk07Gpgn3o#>?%+W{ny3xo*&BUe$M`zS_g9zC*D>UgDPE(XpMGD`3LH(wokp49< zdNWj1#d*wePn+0&yf-EWQ)FY%WTlsEvlX*j^H0UZs!n}l!X(;9g2t*@xDY8}IZWT9 zDU2P;^#35`?{7G}De(-V+0r;_{^Pd0i<@VZ#D-QPi8+Ty ztj^Hk<8;ROVub3+S&chDXPU>rC?5HV8p3<0)Mle*nC|-3x@|fy!(QR?j+v;d!h{!N^%T)iPhvK3?OZ~G})Lfq(fAZQxYz`M^k4?7WYo8iUz&XR+v6;AoWA;H>5F870fQ}yD+c2|SmCu}1! zPGO=(US)#iQeQWfP1{Moa334;Wa%!^7$-AJdsL$M%dyA|4*r>M#=@S2f_FZPr&0J< zM8pp>5fuxc%2Iy*LkIc>$`#anpbA*{+~z{(9k~+Mf9tnHSO~f-#0yhzR0Pp;CEj6J zqdQ1)vt&O_Gm*_))j~-gi>31>{>jgg|x}h8_b73^^OU`si>^hX3 zhK(I0+{=yV+Ciqx`gK2{d&L4pbLv%#+se}2i;9Pxk1ZLokP&QrE9ggEM3RE{>*czJ z0qMV{*}Qn?q`hHLQ^1YiR7!j!_=Rva+SUbSe0)2g+So82qHg7#vb#7o6s2>)`XY5S zZ4m;PJZf{`e9q~?jXe(CJ$5BwuEipjK2A-=N~;TRO@_UAc9Ts)w1u8I>#Q*aduMGP z8yhNYbj^(0{765Ou;cstqMp($%@>2prP7eUB1-YNMfr{D{mU4RJ?!?P&0A5&C@r4| z*I{P>3oRBGPCZ&)M9GKC4|%rh4$w_i%BJfuy=ulWvpMd^=&_IkAKeZ(@ZK(;gzcJB zMn>cJ&UA=u^ke=aFXnIaF5C#k9r?E3ORJ;v@4vbg)V<+!VD^etu~2 z;#z~p2l;zW5x?uM^59IW%!5NOl`7a_Q3B2=w~ZAFHB*#QwQ~a>!*rv_9AiiG6PWVT z{WA3TupNQTbijoNLki~K^(d%4Ug1OfIisrB$k!|OQjOwAawa4q`o16-xzQtA>k(VQ z#HiBP!=O=54_&wvhZNBR_Rgr3R^` zl)RXfeYmqhlyNRZFi&4RHuY#>>aqHDV9y_f4bh)H(wxK*!{Hmr| z@OHUW#?EFVn_f70YM?y0JQEb7DEFp?TAbM=s;F7-5^(Ta5vgn&>9G5umzuER?MiC$ zUxXjX=jJ@RiiKu2#vTlKq4b6YeEz!Aj>+M$Jzr1+bTkIV7uN}4oQj4d9M~sBXWtDN zr99#ib@MUyYV_X0*+h$PnN6 zL~-y(RR2vos6%raub}X?fPiGHDq{L&oBjV&pzN-~g}7Bj?L;D+S3Z={{Hh}7EB_(W z4DQAlU-gJl9vuf1374H-UqvfLW9ioo>#iZh{vbvW_3_QDudB0M17);_q}Wfe9LKJL bEwI?ZfTMB8laJ?G}b zrmWJW$$Bj7ocH^E-gAzpo;xPKeE5grt4}5mHJ^zQ3_|IJ<}+o1L}I| zVPwz6rSDEB4^1aUf`wouNCX=}CKM3tghGOY;3Oyn7r{;N5LAMf;3H@RKOsOUA_NJ= zgb*Q2h!CO#oltUNIvLyDg|Af_#Tf4_kkI@)mk%>`aUD(+i{be-2YR=9rLmO>1%4y0 zLLH7(W6dec7G%>_sjy#22|I*#p#`@LJvf@av=`%Pd%`m)bPBs@ub=i(LO0HC)gyDe zWf(4WNLstlEwl*(ga#7o5_*MB-1&_qfa^w6~Cxl+em#g?8Ms_%v?8`w&^~Brq=>%n!bK&Hf;ZeH)C$4$0Lo^pGMc@d%%) zv;@VQqkWeSunfu~Ey8|MY!P+~Wx{H~pii0b7B_Vhx2hA*Ve+R=DMGoC{y?78T4qmZ znKy;{e~6MT&1_+Ng&xB{s}s+l{3kRg}#qP*ZSG58!PlUepzQpVC>20 z8Z(97a*GrnpzwQm2+ZX|pfwbdxsw})Axfd{dj!0WV(7+=Y%>G1@t<)^kJ*I~i*ivt|NRb_+(A?gKRiy29TL9kqgtp6SKxsU(M6Rt8c54Crw2f9Pow)tR1V~Gu$B~d3$sk+Lfw1) za&VB$4$>Bzx@um%{p6+-6GtZpvpqm88-h)>Fc3UXwC!r&Fi6CR{ha}0+wfkcdPg&i>YHXDTHVn6k0L9n!w+&n>I z9XZw-sLOM!3!)x#k@3}Rz~6fGNKOwcFN#vo?7umibrk(xl5G)s(6^o{b-h+!Yi7=5 z1G%ydoH=Zsxa80`awVOtvFxOxW}#{wabvbaN8i9g1d32hzHZD-Q7o$oyV>7{i(<;lsbi71X~U?>1R;^Z`ji zd8rZpVgnVallaBPf=$#;{)_oFglTSaKkc`%E{-n0tl^V7$$__Wm6od&X#j`mWYs(F`^mr8NggwQ0mVVBF>X~=VuL_KBw>rccOuQUhjHy*Y)nia`=0YYG z_*YAVm@@I?Fx#{*?DktzVWAt2#?Ky{_)6W78bi^kbS{r$K(~8Lu|vy zU#tyy=c-$FvXiQdF7H8M2abIw8pPCUYT3y#dOf_EDRHAY5~aLPDt;DLEr0NgiK2|B z59vN^e9vva2YobrPt^Q;0~ZS{Ze$xN*r76~8?T>s8F=NEA>Us`jABgfq;J-UCefgk z8Jd(CYB+--G;-#ev@G2e)CyXOqeN*e-$u`gp)Xjs@qh~V2(@IPn=YM^#{x^VM@VW1 zsk0|Zntrq1-dJpC=v@?$@NY+a1&*G#79(>oZb$lD+=mSx``oUaMiAj1_?fbv*X}D` zDa3|7E!6O)rYXz@vdWm#f>gH=K<&pqC6K31-uF>QDIie`=2t?+SL}Rv_y!utY zwZ`#MdGUQ?XG3%(lm^^5-4s)B0&-A*t4+}XW`VHIXOdLnRs+T zPiz|c1f}a&04a%lOknL>og|*PJOCf;RpdvdZ1}O^4<6+%!e2|4J!H;B z^KewfrGr`&#(BCVKIw3&`S-)U2K+8s|DUEjg!>lJl(Tyj8&}!n0ycx}j=feuPqtrI z*w|gsCM>1HwujFs{o%l;8Rw?i(N(Zz+ERq%lwT235RXJ_*arLC797ZL1|19sdu~qbK43 delta 177 zcmWl~yA6Us0D$3~AcAkj2fk4lgIrvN0c3y+u`%&Nsf8A9U}bj=1(O&jG5!``zGrb2 zpT|YKa(T;LTUVup|8+QMz=ek diff --git a/Documentation/Strings-zh-chs.json b/Documentation/Strings-zh-chs.json index 9efa1b40becfa48bae1885704d58305d7b0d80df..bfc02a543f217e8f28dff2bbc8ce8dec5cb7b402 100644 GIT binary patch delta 3269 zcmbVOYiv{J8UBuae0;wiJHDLQK6VjeEp%jE>Dr}($Y|MFd<+`N5zj02ovhMEmPA@jaHyq(6)|sknR1>aV}(& zwocab!{@zx@8x;l@0-l;6VBe+Cybn0z4s|^j1VWNC!X>qYU;mpa`oPms|A9AU?hkH z6G0-F2^NBtU?bQG4uX^5BDe`2f|uYU$OJzjKqw&u2_Zt55Fsdp(i10FM~_~{L_QXR z8ZwI*@;iOF`J7<`Vn!h{_sxcMCh0NvusYVrnsDr-@Z#VBMKaSL*1?)_X}=Q0swbp! zjP@D6iIH1I(Vb)4SsST$lSU&;gO4K?h!0995O}cAtE6>~3k^4s0!a9(+Y7gSILy8aCx0DiWy{4?<7A*zUB09TS z7pY~7kz7BlC|G)4*w1C=J#=6*YsZ?4N&u_=D(=F_Vfzv*g-q%_BzWT@B`01mZs7iE zQBiLc%{g+$jmlmoqo4KoiiwXaPRZV;WkQzhaR|f8PAv>@k#m8FZNW<~D^aM!iVGLc zDV4g~WUE7T_K^23?R;LiGN&P|7p~%OPRW{{3+&6kQo;)Y8#-bT1KpHOnuo^L@v3XZ zX+-oWWxVb6*+aM{)nrz41M9}6oKeQIqe`tF#&o?)w5F)IJGd8=o$v;=ih&EXLsG1h zCD{W^rC*XgOfJ0T>iyih9iDpyG2BGfDb~9{sfzy7SA6)sdY`7~ZD%=(aUn}q$cCUm z-8?V6fYE71v}Y**&I}&Lz?+mXZyXu|2JwzYA`Q<`r_p>ckI>hm1Y_3>8gr;`X9UsO zr3J-(Ie$l8)qJTp+@hVc67fL!QFfmo9HX|p|v2CI(^gy^^+{ja}C4r=Lljj^6=87p)YHZ#^j<%!m z+mejF_4s1-8Y0Q;H!6~DGJRaxs`taDZKr(l0bKVFc5s)!`TCr%^lSnjgry!nYjp+Xpxz7IHs!swJxpfg|nO?Sk zeoTDlCFjzh-XU$o*rQZ{U8L2_x^Vq%(i%Nm71|BNky(Q-k= z=P8c^XQyO8ruMj8&J1}?Y1E@(LD?_ea>3u+@B^U=o&lpx=A_$Av&`24ABAR?#j$s- z!DuUe>Y`74NL99iu#r@nX;yNyKNB}-FHOhobps7H&5=`gR82N*RAkLXI~nuk`JY`} z|12CZ@fNnB|E}03&5Myw&h-zZ08YJYb^MnYxC2~HMQiw)9lRpgOzX8xEqo(XM!e)< z>y667J7c4s7pvpKflShcSHk8>ihkA@Q}0AWICC`O!b4-`YMvMEN~thz6|Eb!`-6&u zfkHG;xM{WEP0{YdD~IF|G9k0Xi(Yhl=gxQ%%Vy#pJn&51MG19dZLK4KXRBlj2IZ&+ zpC2x*n)8j1suB@Ylo>5J{%OcXd7I1m*%kLhBzQ_p5=H|i8w}6ORs1vGteC#bC7O58 zy_Tc9a^Pb%j*2US=+#r+K^5ifY-PLkv{%07e1x0!y&tm)SqPED9rJAQ@>Ve_yMg!4PP7fl|+}*M}BwmR`T0a)wsl`I?GgixK;>ba+k@1Q=5GjjC@~m z;B8(S&+DZUhT0t#r2ia{&~VkU6#M(!H9Qi&cZFVfA5>%<%$8U%emrDBO@-vLK0zlm z(K#LX`)BG}d|Cg(Rkt5WXZ)IsI{BOC=1!vORynyb-YNTry*S9H18YNY5OQt->K1sB_*VQe@Tbs=w~)zdH|OQ>t|__aO= z%kk+%JdPzLsvnn6h5RO!TDTkic6t-c^hRvR%Mk}MKQYoP@5y8hrdz!t%CC8C#uQtJ z%|`+Oy!SmfEx3Bocl$JLo!2#|7~d_u=s%YT;>OV8a%_eALPQGc_YJ@DAEL-xiL?Bf zm__3@a{!sPmr+v)-|Cy6LB2d}P}j+#@WrR^3p0}6 zM^65t$ud4J{H_}7qo>NGvs6UidaJbP#@JbqH}Rn*B0Er5U0Q9_@z9|_gkAm86} td#SZHHIrXHe)DgpjdY@VyJn&PD{GLy$Kb=r3PIXCnogV;O(%!K{{oX{=5+u7 delta 192 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 delta 192 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 FO8}dXGr0f& diff --git a/Documentation/Strings-zh-hant.json b/Documentation/Strings-zh-hant.json index 5318f2117e35e55a0a640342a7e4da73853ba649..404e2bc46cbd9095b3d02e065221f0d641b7f52f 100644 GIT binary patch delta 3003 zcmb_eYfO`86n?+<3_MvKtHBSmaHmys8+`S1bIDV9*!qU6UrrwJq=`%-CNPDDH zz`O ziw8>rUMx~|QkHM{^HX?kBwa^qwymFnE z(>9`~<|vI-hO+}94c`_qtMy>C7#~OV$AcRFiz+VB&R0sEY=^+8s@Q*8Q%x`A5f{V^ z7e)}=q${e`^d5`_5;m(DT{{uC*kUp){f;$dv!$GsvKbWL_+-x=38w;=QA_Q@f*bjC z^LcJ(SZ!%H&|bFGXIm+F-kbbGsGQ_Mw`UIDwf!AA`3-6`>zi9jR*O zd4h8kR6KGsLNSAO2-i-`E~s$t3vG+uya1a}iw&=XtsgS^6lQuf*Mn#@#M#X+eW}eG z-ZMiRQ`oEqzE5gU+#j;cZCBzl~Ne57fF>A@Ha_5zgOtYScw|~dqv6= zrf+RoCeh;xPcSdpmgBR)s7Rb2wvz|#iqOuEP*Mvu)93G>wQ!i8n0581AaGakDqOD@ z$%JSEqg@GVsI-d3snPzaHYm>Pium^d2KOkX%tpA=q*(xp2`!tXsk(n?d3U%Nm_EfC zW;H<&NKRw_uDG@z1_R;^G)LT*9QalWSjlGoalyhM>wC3&8Ff?L5O&nz9XqhpE_)^_ z6WaIgeg=6}5_@a$LQi!AJ@4>B<~wS`tTX61qfBF|i^xDMG$~jk7~f)4Ff=UX687Mm z<^j`hXM$O`~C9#TS}XYh;%Mxl>2lJ830Dwhk|~ zkUhKBmE1JanMrmLeb<>?w@)^nLP&QzcMFyP@Y>u^OH}Rf^d^ ze195(Xwz$aRp=YEk%!ccyfNb#I8Z2S?kHF#Z?uCNQT+bjY9M!mxFDRUB$!DUXBjOs z9M->G!gGM%(vME7(i7<|9Rf@W9-0N4X{|C*9ii`iH3WtjUF=_;lM_;z+Uv9lK4WE=sRf&{&iaveUOjY#x0pC-(>UnU)c?9|hFfB0JE5w;rC{*zN8nsLm z1vIYqBv2ifyigwQ(DO;LwWMwLf2DXF!EwRL=`$B(DBZo5D@v{SQzPS}%a0#EShh-* z1nyz2hRrR_GyCSD;s6oIz$Y?i?i4$2(;kQKqzr2RxmE99~1;z^nX~UE0 Iy2@Yw1@d*L*#H0l delta 126 zcmbRCBI7_$#)cr>$vFxI}mdKF((jn0WmiagVgc@F&_}~ z1F--Q3j(na5DNpb2oQ?`u^14GZ~t&r;+0EtfNpz$F5~t9U8YGZwr}u|OnVIgbLT|h diff --git a/Documentation/Strings-zh.json b/Documentation/Strings-zh.json index 9efa1b40becfa48bae1885704d58305d7b0d80df..bfc02a543f217e8f28dff2bbc8ce8dec5cb7b402 100644 GIT binary patch delta 3269 zcmbVOYiv{J8UBuae0;wiJHDLQK6VjeEp%jE>Dr}($Y|MFd<+`N5zj02ovhMEmPA@jaHyq(6)|sknR1>aV}(& zwocab!{@zx@8x;l@0-l;6VBe+Cybn0z4s|^j1VWNC!X>qYU;mpa`oPms|A9AU?hkH z6G0-F2^NBtU?bQG4uX^5BDe`2f|uYU$OJzjKqw&u2_Zt55Fsdp(i10FM~_~{L_QXR z8ZwI*@;iOF`J7<`Vn!h{_sxcMCh0NvusYVrnsDr-@Z#VBMKaSL*1?)_X}=Q0swbp! zjP@D6iIH1I(Vb)4SsST$lSU&;gO4K?h!0995O}cAtE6>~3k^4s0!a9(+Y7gSILy8aCx0DiWy{4?<7A*zUB09TS z7pY~7kz7BlC|G)4*w1C=J#=6*YsZ?4N&u_=D(=F_Vfzv*g-q%_BzWT@B`01mZs7iE zQBiLc%{g+$jmlmoqo4KoiiwXaPRZV;WkQzhaR|f8PAv>@k#m8FZNW<~D^aM!iVGLc zDV4g~WUE7T_K^23?R;LiGN&P|7p~%OPRW{{3+&6kQo;)Y8#-bT1KpHOnuo^L@v3XZ zX+-oWWxVb6*+aM{)nrz41M9}6oKeQIqe`tF#&o?)w5F)IJGd8=o$v;=ih&EXLsG1h zCD{W^rC*XgOfJ0T>iyih9iDpyG2BGfDb~9{sfzy7SA6)sdY`7~ZD%=(aUn}q$cCUm z-8?V6fYE71v}Y**&I}&Lz?+mXZyXu|2JwzYA`Q<`r_p>ckI>hm1Y_3>8gr;`X9UsO zr3J-(Ie$l8)qJTp+@hVc67fL!QFfmo9HX|p|v2CI(^gy^^+{ja}C4r=Lljj^6=87p)YHZ#^j<%!m z+mejF_4s1-8Y0Q;H!6~DGJRaxs`taDZKr(l0bKVFc5s)!`TCr%^lSnjgry!nYjp+Xpxz7IHs!swJxpfg|nO?Sk zeoTDlCFjzh-XU$o*rQZ{U8L2_x^Vq%(i%Nm71|BNky(Q-k= z=P8c^XQyO8ruMj8&J1}?Y1E@(LD?_ea>3u+@B^U=o&lpx=A_$Av&`24ABAR?#j$s- z!DuUe>Y`74NL99iu#r@nX;yNyKNB}-FHOhobps7H&5=`gR82N*RAkLXI~nuk`JY`} z|12CZ@fNnB|E}03&5Myw&h-zZ08YJYb^MnYxC2~HMQiw)9lRpgOzX8xEqo(XM!e)< z>y667J7c4s7pvpKflShcSHk8>ihkA@Q}0AWICC`O!b4-`YMvMEN~thz6|Eb!`-6&u zfkHG;xM{WEP0{YdD~IF|G9k0Xi(Yhl=gxQ%%Vy#pJn&51MG19dZLK4KXRBlj2IZ&+ zpC2x*n)8j1suB@Ylo>5J{%OcXd7I1m*%kLhBzQ_p5=H|i8w}6ORs1vGteC#bC7O58 zy_Tc9a^Pb%j*2US=+#r+K^5ifY-PLkv{%07e1x0!y&tm)SqPED9rJAQ@>Ve_yMg!4PP7fl|+}*M}BwmR`T0a)wsl`I?GgixK;>ba+k@1Q=5GjjC@~m z;B8(S&+DZUhT0t#r2ia{&~VkU6#M(!H9Qi&cZFVfA5>%<%$8U%emrDBO@-vLK0zlm z(K#LX`)BG}d|Cg(Rkt5WXZ)IsI{BOC=1!vORynyb-YNTry*S9H18YNY5OQt->K1sB_*VQe@Tbs=w~)zdH|OQ>t|__aO= z%kk+%JdPzLsvnn6h5RO!TDTkic6t-c^hRvR%Mk}MKQYoP@5y8hrdz!t%CC8C#uQtJ z%|`+Oy!SmfEx3Bocl$JLo!2#|7~d_u=s%YT;>OV8a%_eALPQGc_YJ@DAEL-xiL?Bf zm__3@a{!sPmr+v)-|Cy6LB2d}P}j+#@WrR^3p0}6 zM^65t$ud4J{H_}7qo>NGvs6UidaJbP#@JbqH}Rn*B0Er5U0Q9_@z9|_gkAm86} td#SZHHIrXHe)DgpjdY@VyJn&PD{GLy$Kb=r3PIXCnogV;O(%!K{{oX{=5+u7 delta 192 zcmWl~y9$C(0Dxi4OJ!De&u(gyW3+ajprN5X=v^F?CZpkiM6|d@n;;DhJwgxAiv;}* zAK&YHP(H~)xj%%wu3b%Dgkj3t#`ol6Wr*Y6IwheN#i@$_It-LiK@}!yuuw+>P1tCm wjSjl#p^pKEa4^Cc6HGCKo5!iQe20aI3ZbBs7ed|c function Get-ModuleVersion { - '3.1.6' + '3.1.7' } function Invoke-InitializeModule @@ -27,6 +27,7 @@ function Invoke-InitializeModule Title = "Application" Key = "EMAzureApp" Type = "List" + SelectedValuePath = "ClientId" ItemsSource = $global:MSGraphGlobalApps DefaultValue = "" SubPath = "EndpointManager" @@ -72,22 +73,6 @@ function Invoke-InitializeModule SubPath = "EndpointManager" }) "EndpointManager" - Add-SettingsObject (New-Object PSObject -Property @{ - Title = "Show Delete button" - Key = "EMAllowDelete" - Type = "Boolean" - DefaultValue = $false - Description = "Allow deleting individual objectes" - }) "EndpointManager" - - Add-SettingsObject (New-Object PSObject -Property @{ - Title = "Show Bulk Delete " - Key = "EMAllowBulkDelete" - Type = "Boolean" - DefaultValue = $false - Description = "Allow using bulk delete to delete all objects of selected types" - }) "EndpointManager" - $viewPanel = Get-XamlObject ($global:AppRootFolder + "\Xaml\EndpointManagerPanel.xaml") -AddVariables Set-EMViewPanel $viewPanel @@ -157,6 +142,7 @@ function Invoke-InitializeModule PostFileImportCommand = { Start-PostFileImportEndpointSecurity @args } #PreCopyCommand = { Start-PreCopyEndpointSecurity @args } PostCopyCommand = { Start-PostCopyEndpointSecurity @args } + PreUpdateCommand = { Start-PreUpdateEndpointSecurity @args } Permissons=@("DeviceManagementConfiguration.ReadWrite.All") GroupId = "EndpointSecurity" }) @@ -170,6 +156,7 @@ function Invoke-InitializeModule Permissons=@("DeviceManagementConfiguration.ReadWrite.All") Dependencies = @("Locations","Notifications") PostExportCommand = { Start-PostExportCompliancePolicies @args } + PreUpdateCommand = { Start-PreUpdateCompliancePolicies @args } GroupId = "CompliancePolicies" }) @@ -222,6 +209,9 @@ function Invoke-InitializeModule PreImportCommand = { Start-PreImportESP @args } PostExportCommand = { Start-PostExportESP @args } PreDeleteCommand = { Start-PreDeleteEnrollmentRestrictions @args } # Note: Uses same PreDelete as restrictions + PreReplaceCommand = { Start-PreReplaceEnrollmentRestrictions @args } # Note: Uses same PreReplaceCommand as restrictions + PostReplaceCommand = { Start-PostReplaceEnrollmentRestrictions @args } # Note: Uses same PostReplaceCommand as restrictions + PreFilesImportCommand = { Start-PreFilesImportEnrollmentRestrictions @args } # Note: Uses same PreFilesImportCommand as restrictions QUERYLIST = "`$filter=endsWith(id,'Windows10EnrollmentCompletionPageConfiguration')" Permissons=@("DeviceManagementServiceConfig.ReadWrite.All") SkipRemoveProperties = @('Id') @@ -238,6 +228,9 @@ function Invoke-InitializeModule PostExportCommand = { Start-PostExportEnrollmentRestrictions @args } PreImportCommand = { Start-PreImportEnrollmentRestrictions @args } PreDeleteCommand = { Start-PreDeleteEnrollmentRestrictions @args } + PreReplaceCommand = { Start-PreReplaceEnrollmentRestrictions @args } + PostReplaceCommand = { Start-PostReplaceEnrollmentRestrictions @args } + PreFilesImportCommand = { Start-PreFilesImportEnrollmentRestrictions @args } Permissons=@("DeviceManagementServiceConfig.ReadWrite.All") SkipRemoveProperties = @('Id') AssignmentsType = "enrollmentConfigurationAssignments" @@ -387,6 +380,7 @@ function Invoke-InitializeModule CopyDefaultName = "%displayName% Copy" # '-' is not allowed in the name Permissons=@("DeviceManagementServiceConfig.ReadWrite.All") PreImportAssignmentsCommand = { Start-PreImportAssignmentsAutoPilot @args } + PreDeleteCommand = { Start-PreDeleteAutoPilot @args } GroupId = "WinEnrollment" }) @@ -834,6 +828,58 @@ function Start-PostCopyEndpointSecurity } } +function Start-PreUpdateEndpointSecurity +{ + param($obj, $objectType, $curObject, $fromObj) + + if(-not $fromObj.settings) { return } + + $strAPI = "/deviceManagement/intents/$($curObject.Object.id)/updateSettings" + + $curObject = Get-GraphObject $curObject.Object $objectType + + $curValues = @() + foreach($val in $curObject.Object.settings) + { + if($fromObj.settings | Where { $_.definitionId -eq $val.definitionId}) { continue } + + # Set all existing values to null + # Note: This will not remove them from the configured list just set them Not Configured + $curValues += [PSCustomObject]@{ + '@odata.type' = $val.'@odata.type' + definitionId = $val.definitionId + id = $val.id + valueJson = "null" + } + } + + $curValues += $fromObj.settings + + <# + if($curValues.Count -gt 0) + { + $tmpObj = [PSCustomObject]@{ + settings = $curValues + } + $json = ConvertTo-Json $tmpObj -Depth 10 + + # Set all existing values to null + # Note: This will not remove them from the configured list just set them Not Configured + Invoke-GraphRequest -Url $strAPI -Content $json -HttpMethod "POST" | Out-Null + } + #> + + $tmpObj = [PSCustomObject]@{ + settings = $curValues + } + Start-GraphPreImport $tmpObj.settings + + $json = ConvertTo-Json $tmpObj -Depth 10 + Invoke-GraphRequest -Url $strAPI -Content $json -HttpMethod "POST" | Out-Null + + Remove-Property $obj "templateId" +} + #endregion #region @@ -892,6 +938,23 @@ function Start-PostExportCompliancePolicies } } } + +function Start-PreUpdateCompliancePolicies +{ + param($obj, $objectType, $curObject, $fromObj) + + $strAPI = "/deviceManagement/deviceCompliancePolicies/$($curObject.Object.id)/scheduleActionsForRules" + + $tmpObj = [PSCustomObject]@{ + deviceComplianceScheduledActionForRules = $obj.scheduledActionsForRule + } + + $json = ConvertTo-Json $tmpObj -Depth 10 + Invoke-GraphRequest -Url $strAPI -Content $json -HttpMethod "POST" | Out-Null + + Remove-Property $obj "scheduledActionsForRule" +} + #endregion #region Intune Branding functions @@ -1242,7 +1305,7 @@ function Start-PostImportAppProtection $tmp = $newObject."@odata.type".Split('.')[-1] $objectClass = Get-GraphObjectClassName $tmp - $response = Invoke-GraphRequest -Url "/deviceAppManagement/$objectClass/$($obj.Id)/targetApps" -Content "{ apps: $(ConvertTo-Json $global:ImportObjectInfo.Apps -Depth 10)}" -HttpMethod POST + Invoke-GraphRequest -Url "/deviceAppManagement/$objectClass/$($obj.Id)/targetApps" -Content "{ apps: $(ConvertTo-Json $global:ImportObjectInfo.Apps -Depth 10)}" -HttpMethod POST | Out-Null } catch {} } @@ -1504,12 +1567,63 @@ function Start-PreImportPolicySets { foreach($prop in ($item.PSObject.Properties | Where {$_.Name -notin $keepProperties})) { - #if($prop.Name -in $keepProperties) { continue } Remove-Property $item $prop.Name } #@("itemType","displayName","status","errorCode") | foreach { Remove-Property $item $_ } } } + +function Update-EMPolicySetAssignment +{ + param($assignment, $sourceObject, $newObject, $objectType) + + $api = "/deviceAppManagement/policySets/$($assignment.SourceId)?`$expand=assignments,items" + + $psObj = Invoke-GraphRequest -Url $api -ODataMetadata "Minimal" + + if(-not $psObj) + { + return + } + + $curItem = $psObj.Items | Where payloadId -eq $sourceObject.Id + + if(-not $curItem) + { + return + } + + $api = "/deviceAppManagement/policySets/$($assignment.SourceId)/update" + + $curItemClone = $curItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json + $newItem = $curItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json + $newItem.payloadId = $newObject.Id + if($newItem.guidedDeploymentTags -is [String] -and [String]::IsNullOrEmpty($newItem.guidedDeploymentTags)) + { + $newItem.guidedDeploymentTags = @() + } + + $keepProperties = @('@odata.type','payloadId','Settings','guidedDeploymentTags') + #itemType? e.g. #microsoft.graph.iosManagedAppProtection + #priority? + + foreach($prop in ($newItem.PSObject.Properties | Where {$_.Name -notin $keepProperties})) + { + Remove-Property $newItem $prop.Name + } + + $update = @{} + $update.Add('addedPolicySetItems',@($newItem)) + $update.Add('updatedPolicySetItems', @()) + $update.Add('deletedPolicySetItems',@($curItemClone.Id)) + + $json = $update | ConvertTo-Json -Depth 10 + + Write-Log "Update PolicySet $($psObj.displayName) - Replace: $((Get-GraphObjectName $newObject $objectType))" + + Invoke-GraphRequest -Url $api -HttpMethod "POST" -Content $json +} + #endregion #endregion Locations @@ -1718,6 +1832,38 @@ function Start-PreDeleteEnrollmentRestrictions @{ "Delete" = $false } } } + +function Start-PreReplaceEnrollmentRestrictions +{ + param($obj, $objectType, $sourceObj, $fromFile) + + if($sourceObj.Priority -eq 0) { @{ "Replace" = $false } } +} + +function Start-PostReplaceEnrollmentRestrictions +{ + param($obj, $objectType, $sourceObj, $fromFile) + + if($sourceObj.Priority -eq 0) { return } + + $api = "/deviceManagement/deviceEnrollmentConfigurations/$($obj.id)/setpriority" + + $priority = [PSCustomObject]@{ + priority = $sourceObj.Priority + } + $json = $priority | ConvertTo-Json -Depth 10 + + Write-Log "Update priority for $($obj.displayName) to $($sourceObj.Priority)" + Invoke-GraphRequest $api -HttpMethod "POST" -Content $json +} + +function Start-PreFilesImportEnrollmentRestrictions +{ + param($objectType, $filesToImport) + + $filesToImport | sort-object -property @{e={$_.Object.priority}} +} + #endregion #region ScopeTags @@ -1736,6 +1882,32 @@ function Start-PreImportAssignmentsAutoPilot Add-EMAssignmentsToObject $obj $objectType $file $assignments } + +function Start-PreDeleteAutoPilot +{ + param($obj, $objectType) + + Write-Log "Delete AutoPilot profile assignments" + + if(-not $obj.Assignments) + { + $tmpObj = (Get-GraphObject $obj $objectType).Object + } + else + { + $tmpObj = $obj + } + + foreach($assignment in $tmpObj.Assignments) + { + if($assignment.Source -ne "direct") { continue } + + $api = "/deviceManagement/windowsAutopilotDeploymentProfiles/$($obj.Id)/assignments/$($assignment.Id)" + + Invoke-GraphRequest $api -HttpMethod "DELETE" + } +} + #endregion #region Health Scripts diff --git a/Extensions/MSGraph.psm1 b/Extensions/MSGraph.psm1 index b2967a0..06b3cf2 100644 --- a/Extensions/MSGraph.psm1 +++ b/Extensions/MSGraph.psm1 @@ -10,7 +10,7 @@ This module manages Microsoft Grap fuctions like calling APIs, managing graph ob #> function Get-ModuleVersion { - '3.1.2' + '3.1.3' } $global:MSGraphGlobalApps = @( @@ -26,6 +26,25 @@ function Invoke-InitializeModule $global:LoadedDependencyObject = $null $global:MigrationTableCache = $null + $script:lstImportTypes = @( + [PSCustomObject]@{ + Name = "Always import" + Value = "alwaysImport" + }, + [PSCustomObject]@{ + Name = "Skip if object exists" + Value = "skipIfExist" + }, + [PSCustomObject]@{ + Name = "Replace (Preview)" + Value = "replace" + }, + [PSCustomObject]@{ + Name = "Update (Experimental)" + Value = "update" + } + ) + # Make sure MS Graph settings are added before exiting before App Id and Tenant Id is missing Write-Log "Add settings and menu items" @@ -83,12 +102,53 @@ function Invoke-InitializeModule Description = "Convert AD synched groups to Azure AD group during import if the group does not exist" }) "ImportExport" + Add-SettingsObject (New-Object PSObject -Property @{ + Title = "Import type" + Key = "ImportType" + Type = "List" + ItemsSource = $script:lstImportTypes + DefaultValue = "alwaysImport" + }) "ImportExport" + Add-SettingsObject (New-Object PSObject -Property @{ Title = "Import Assignments" Key = "ImportAssignments" Type = "Boolean" DefaultValue = $true - Description = "Import assignments when importing objects" + Description = "Default value for Import assignments when importing objects" + }) "ImportExport" + + Add-SettingsObject (New-Object PSObject -Property @{ + Title = "Import Scope (Tags)" + Key = "ImportScopeTags" + Type = "Boolean" + DefaultValue = $true + Description = "Default value for Import Scope (Tags) when importing objects" + }) "ImportExport" + + Add-SettingsObject (New-Object PSObject -Property @{ + Title = "Show Delete button" + Key = "EMAllowDelete" + Type = "Boolean" + DefaultValue = $false + Description = "Allow deleting individual objectes" + }) "ImportExport" + + Add-SettingsObject (New-Object PSObject -Property @{ + Title = "Show Bulk Delete " + Key = "EMAllowBulkDelete" + Type = "Boolean" + DefaultValue = $false + Description = "Allow using bulk delete to delete all objects of selected types" + }) "ImportExport" + + + Add-SettingsObject (New-Object PSObject -Property @{ + Title = "Allow update on import (Preview)" + Key = "AllowUpdate" + Type = "Boolean" + DefaultValue = $false + Description = "This will enable the option to update/replace an existing object during import" }) "ImportExport" } @@ -127,6 +187,7 @@ function Get-GraphAppInfo function Invoke-GraphAuthenticationUpdated { $global:MigrationTableCache = $null + $global:MigrationTableCacheId = $null $global:LoadedDependencyObjects = $null $global:migFileObj = $null } @@ -142,7 +203,7 @@ function Invoke-GraphRequest $Headers, - [ValidateSet("GET","POST","OPTIONS","DELETE", "PATCH")] + [ValidateSet("GET","POST","OPTIONS","DELETE", "PATCH","PUT")] [Alias("Method")] $HttpMethod = "GET", @@ -240,6 +301,8 @@ function Invoke-GraphRequest { throw $global:error[0] } + + if($HttpMethod -eq "PATCH" -and [String]::IsNullOrempty($ret)) { $ret = $true } } catch { @@ -255,14 +318,16 @@ function Invoke-GraphRequest function Get-GraphObjects { param( - [Array] + [String] $Url, [Array] $property = $null, [Array] $exclude, $SortProperty = "displayName", - $objectType) + $objectType, + [switch] + $SingleObject) $objects = @() @@ -274,7 +339,29 @@ function Get-GraphObjects $params.Add('ODataMetadata',$objectType.ODataMetadata) } + if(-not $url) + { + $url = $objectType.API + } + + if($SingleObject -ne $true -and $objectType.QUERYLIST) + { + if(($url.IndexOf('?')) -eq -1) + { + $url = "$($url.Trim())?$($objectType.QUERYLIST.Trim())" + } + else + { + $url = "$($url.Trim())&$($objectType.QUERYLIST.Trim())" # Risky...does not check that the parameter is already in use + } + } + $graphObjects = Invoke-GraphRequest -Url $url @params + + if($SingleObject -ne $true -and $objectType.PostListCommand) + { + $graphObjects = & $objectType.PostListCommand $graphObjects $objectType + } if($graphObjects -and ($graphObjects | GM -Name Value -MemberType NoteProperty)) { @@ -341,18 +428,7 @@ function Show-GraphObjects $global:grdTitle.Visibility = "Visible" } - $url = $global:curObjectType.API - if($global:curObjectType.QUERYLIST) - { - $url = "$($url.Trim())?$($global:curObjectType.QUERYLIST.Trim())" - } - - $graphObjects = @(Get-GraphObjects -Url $url -property $global:curObjectType.ViewProperties -objectType $global:curObjectType) - - if($global:curObjectType.PostListCommand) - { - $graphObjects = & $global:curObjectType.PostListCommand $graphObjects $global:curObjectType - } + $graphObjects = @(Get-GraphObjects -property $global:curObjectType.ViewProperties -objectType $global:curObjectType) if(($graphObjects | measure).Count -eq 0) { return } @@ -486,7 +562,7 @@ function Get-GraphObject } elseif($api.IndexOf("`$expand") -gt 1) { - $api = ($api + ",") + $api = ($api + ",") # A bit risky...assumes that expand is last in the existing query } else { @@ -496,7 +572,7 @@ function Get-GraphObject $api = ($api + ($expand -join ",")) } - $objInfo = Get-GraphObjects -Url $api -property $objectType.ViewProperties -objectType $objectType + $objInfo = Get-GraphObjects -Url $api -property $objectType.ViewProperties -objectType $objectType -SingleObject if($objInfo -and $objectType.PostGetCommand) { @@ -746,18 +822,12 @@ function Show-GraphBulkExportForm Write-Log "----------------------------------------------------------------" Write-Log "Export $($item.ObjectType.Title) objects" Write-Log "----------------------------------------------------------------" - - $url = $item.ObjectType.API - if($item.ObjectType.QUERYLIST) - { - $url = "$($url.Trim())?$($item.ObjectType.QUERYLIST.Trim())" - } try { $folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:exportForm "txtExportPath" "Text") (Get-XamlProperty $script:exportForm "chkAddObjectType" "IsChecked") (Get-XamlProperty $script:exportForm "chkAddCompanyName" "IsChecked") - $objects = @(Get-GraphObjects -Url $url -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) + $objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) foreach($obj in $objects) { Write-Status "Export $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force @@ -797,13 +867,44 @@ function Show-GraphImportForm } Set-XamlProperty $script:importForm "txtImportPath" "Text" (?? $path (Get-SettingValue "RootFolder")) + Set-XamlProperty $script:importForm "chkImportAssignments" "IsChecked" (Get-SettingValue "ImportAssignments") + Set-XamlProperty $script:importForm "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags") + Set-XamlProperty $script:importForm "cbImportType" "ItemsSource" $script:lstImportTypes + Set-XamlProperty $script:importForm "cbImportType" "SelectedValue" (Get-SettingValue "ImportType" "alwaysImport") + + if((Get-SettingValue "AllowUpdate") -eq $true) + { + Set-XamlProperty $script:importForm "lblImportType" "Visibility" "Visible" + Set-XamlProperty $script:importForm "cbImportType" "Visibility" "Visible" + } + + $column = Get-GridCheckboxColumn "Selected" + $global:dgObjectsToImport.Columns.Add($column) + + $column.Header.IsChecked = $true # All items are checked by default + $column.Header.add_Click({ + foreach($item in $global:dgObjectsToImport.ItemsSource) + { + $item.Selected = $this.IsChecked + } + $global:dgObjectsToImport.Items.Refresh() + } + ) + + # Add Object type column + $binding = [System.Windows.Data.Binding]::new("fileName") + $column = [System.Windows.Controls.DataGridTextColumn]::new() + $column.Header = "File Name" + $column.IsReadOnly = $true + $column.Binding = $binding + $global:dgObjectsToImport.Columns.Add($column) Add-XamlEvent $script:importForm "browseImportPath" "add_click" ({ $folder = Get-Folder (Get-XamlProperty $script:importForm "txtImportPath" "Text") "Select root folder for import" if($folder) { Set-XamlProperty $script:importForm "txtImportPath" "Text" $folder - $global:lstFiles.ItemsSource = @(Get-GraphFileObjects $folder) + $global:dgObjectsToImport.ItemsSource = @(Get-GraphFileObjects $folder) Save-Setting "" "LastUsedFullPath" $folder Set-XamlProperty $script:importForm "lblMigrationTableInfo" "Content" (Get-MigrationTableInfo) } @@ -817,26 +918,30 @@ function Show-GraphImportForm Add-XamlEvent $script:importForm "btnImportSelected" "add_click" { Write-Status "Import objects" Get-GraphDependencyDefaultObjects - foreach ($fileObj in ($global:lstFiles.ItemsSource | Where Selected -eq $true)) + $allowUpdate = ((Get-SettingValue "AllowUpdate") -eq $true) + $filesToImport = $global:dgObjectsToImport.ItemsSource | Where Selected -eq $true + if($global:curObjectType.PreFilesImportCommand) { - Import-GraphFile $fileObj + $filesToImport = & $global:curObjectType.PreFilesImportCommand $global:curObjectType $filesToImport + } + + foreach ($fileObj in $filesToImport) + { + if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport" -and (Reset-GraphObjet $fileObj $global:dgObjects.ItemsSource)) + { + continue + } + + Import-GraphFile $fileObj } Show-GraphObjects Show-ModalObject Write-Status "" } - Add-XamlEvent $script:importForm "chkCheckAll" "add_click" { - foreach($obj in $global:lstFiles.Items) - { - $obj.Selected = $global:chkCheckAll.IsChecked - } - $global:lstFiles.Items.Refresh() - } - Add-XamlEvent $script:importForm "btnGetFiles" "add_click" { # Used when the user manually updates the path and the press Get Files - $global:lstFiles.ItemsSource = @(Get-GraphFileObjects $global:txtImportPath.Text) + $global:dgObjectsToImport.ItemsSource = @(Get-GraphFileObjects $global:txtImportPath.Text) if([IO.Directory]::Exists($global:txtImportPath.Text)) { Save-Setting "" "LastUsedFullPath" $global:txtImportPath.Text @@ -848,7 +953,7 @@ function Show-GraphImportForm if($global:txtImportPath.Text) { - $global:lstFiles.ItemsSource = @(Get-GraphFileObjects $global:txtImportPath.Text) + $global:dgObjectsToImport.ItemsSource = @(Get-GraphFileObjects $global:txtImportPath.Text) Set-XamlProperty $script:importForm "lblMigrationTableInfo" "Content" (Get-MigrationTableInfo) } @@ -867,7 +972,16 @@ function Show-GraphBulkImportForm } Set-XamlProperty $script:importForm "txtImportPath" "Text" (?? $path (Get-SettingValue "RootFolder")) - #Set-XamlProperty $script:importForm "chkAddCompanyName" "IsChecked" (Get-SettingValue "AddCompanyName") + Set-XamlProperty $script:importForm "chkImportAssignments" "IsChecked" (Get-SettingValue "ImportAssignments") + Set-XamlProperty $script:importForm "chkImportScopes" "IsChecked" (Get-SettingValue "ImportScopeTags") + Set-XamlProperty $script:importForm "cbImportType" "ItemsSource" $script:lstImportTypes + Set-XamlProperty $script:importForm "cbImportType" "SelectedValue" (Get-SettingValue "ImportType" "alwaysImport") + + if((Get-SettingValue "AllowUpdate") -eq $true) + { + Set-XamlProperty $script:importForm "lblImportType" "Visibility" "Visible" + Set-XamlProperty $script:importForm "cbImportType" "Visibility" "Visible" + } Add-XamlEvent $script:importForm "browseImportPath" "add_click" ({ $folder = Get-Folder (Get-XamlProperty $script:importForm "txtImportPath" "Text") "Select root folder for import" @@ -938,6 +1052,8 @@ function Show-GraphBulkImportForm Get-GraphDependencyDefaultObjects $importedObjects = 0 + $allowUpdate = ((Get-SettingValue "AllowUpdate") -eq $true) + foreach($item in ($script:importObjects | where Selected -eq $true | sort-object -property @{e={$_.ObjectType.ImportOrder}})) { Write-Status "Import $($item.ObjectType.Title) objects" -Force @@ -946,10 +1062,34 @@ function Show-GraphBulkImportForm Write-Log "----------------------------------------------------------------" $folder = Get-GraphObjectFolder $item.ObjectType (Get-XamlProperty $script:importForm "txtImportPath" "Text") (Get-XamlProperty $script:importForm "chkAddObjectType" "IsChecked") + $graphObjects = $null + + if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport") + { + try + { + Write-Status "Get $($item.Title) objects" -Force + $graphObjects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) + } + catch {} + } + if([IO.Directory]::Exists($folder)) { - foreach ($fileObj in @(Get-GraphFileObjects $folder -ObjectType $item.ObjectType)) + $filesToImport = Get-GraphFileObjects $folder -ObjectType $item.ObjectType + if($item.ObjectType.PreFilesImportCommand) { + $filesToImport = & $item.ObjectType.PreFilesImportCommand $item.ObjectType $filesToImport + } + + foreach ($fileObj in @($filesToImport)) + { + if($allowUpdate -and $global:cbImportType.SelectedValue -ne "alwaysImport" -and $graphObjects -and (Reset-GraphObjet $fileObj $graphObjects)) + { + $importedObjects++ + continue + } + Import-GraphFile $fileObj $importedObjects++ } @@ -1111,19 +1251,13 @@ function Show-GraphBulkDeleteForm Write-Log "----------------------------------------------------------------" Write-Log "Delete $($item.ObjectType.Title) objects" Write-Log "----------------------------------------------------------------" - - $url = $item.ObjectType.API - if($item.ObjectType.QUERYLIST) - { - $url = "$($url.Trim())?$($item.ObjectType.QUERYLIST.Trim())" - } try { Write-Status "Get $($item.Title) objects" -Force - $objects = @(Get-GraphObjects -Url $url -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) + $objects = @(Get-GraphObjects -property $item.ObjectType.ViewProperties -objectType $item.ObjectType) foreach($obj in $objects) - { + { Write-Status "Delete $($item.Title): $((Get-GraphObjectName $obj.Object $obj.ObjectType))" -Force Remove-GraphObject $obj.Object $obj.ObjectType $folder } @@ -1225,80 +1359,236 @@ function Import-GraphFile if($newObj -and $objClone.Assignments -and $global:chkImportAssignments.IsChecked -eq $true) { - $preConfig = $null - if($file.ObjectType.PreImportAssignmentsCommand) + Import-GraphObjectAssignment $newObj $file.ObjectType $objClone.Assignments $file.FileInfo.FullName | Out-Null + } + } + catch + { + Write-LogError "Failed to import file '$($file.FileInfo.Name)'" $_.Exception + } +} + +function Reset-GraphObjet +{ + param($fileObj, $objectList) + + $nameProp = ?? $fileObj.ObjectType.NameProperty "displayName" + $curObject = $objectList | Where { $_.Object.$nameProp -eq $fileObj.Object.$nameProp -and $_.Object.'@OData.Type' -eq $fileObj.Object.'@OData.Type' } + + if($global:cbImportType.SelectedValue -eq "skipIfExist" -and ($curObject | measure).Count -gt 0) + { + Write-Log "Objects with name $($fileObj.Object.$nameProp) already exists. Object will not be imported" + return $true + } + elseif(($curObject | measure).Count -gt 1) + { + Write-Log "Multiple objects return with name $($fileObj.Object.$nameProp). Object will not be imported or replaced" 2 + return $true + } + elseif(($curObject | measure).Count -eq 1) + { + Write-Log "Update $((Get-GraphObjectName $fileObj.Object $fileObj.ObjectType)) with id $($curObject.Object.Id)" + $objectType = $fileObj.ObjectType + + # Clone the object before removing properties + $obj = $fileObj.Object | ConvertTo-Json -Depth 10 | ConvertFrom-Json + Start-GraphPreImport $obj $objectType + Remove-Property $obj "Assignments" + Remove-Property $obj "isAssigned" + + if($global:cbImportType.SelectedValue -eq "update") + { + $params = @{} + $strAPI = (?? $objectType.APIPATCH $objectType.API) + "/$($curObject.Object.Id)" + $method = "PATCH" + if($objectType.PreUpdateCommand) { - $preConfig = & $file.ObjectType.PreImportAssignmentsCommand $newObj $file.ObjectType $file.FileInfo.FullName $objClone.Assignments + $ret = & $objectType.PreUpdateCommand $obj $objectType $curObject $fileObj.Object + if($ret -is [HashTable]) + { + if($ret.ContainsKey("Import") -and $ret["Import"] -eq $false) + { + # Import handled manually + return $false + } + + if($ret.ContainsKey("API")) + { + $strAPI = $ret["API"] + } + + if($ret.ContainsKey("Method")) + { + $method = $ret["Method"] + } + + if($ret.ContainsKey("AdditionalHeaders") -and $ret["AdditionalHeaders"] -is [HashTable]) + { + $params.Add("AdditionalHeaders",$ret["AdditionalHeaders"]) + } + } } - ###### Import Assignments ###### - - if($preConfig -isnot [Hashtable]) { $preConfig = @{} } + $json = ConvertTo-Json $obj -Depth 10 + if($true) #$global:MigrationTableCacheId -ne $global:Organization.Id) + { + # Call Update-JsonForEnvironment before importing the object + # E.g. PolicySets contains references, AppConfiguration policies reference apps etc. + $json = Update-JsonForEnvironment $json + } - if($preConfig["Import"] -eq $false) { return } # Assignment managed manually so skip further processing + $objectUpdated = (Invoke-GraphRequest -Url $strAPI -Content $json -HttpMethod $method @params) - $api = ?? $preConfig["API"] "$($file.ObjectType.API)/$($newObj.Id)/assign" + if($objectUpdated) + { + Write-Log "Object updated successfully" + } - $method = ?? $preConfig["Method"] "POST" + if($objectUpdated -and $objectType.PostUpdateCommand) + { + # Reload the updated object + $updatedObject = Get-GraphObject $curObject.Object $objectType + & $objectType.PostUpdateCommand $updatedObject $fileObj + } + return $true + } + elseif($global:cbImportType.SelectedValue -eq "replace") + { + $replace = $true + $import = $true + $delete = $true - $keepProperties = ?? $file.ObjectType.AssignmentProperties @("target") - $keepTargetProperties = ?? $file.ObjectType.AssignmentTargetProperties @("@odata.type","groupId") - $ObjectAssignments = @() - foreach($assignment in $objClone.Assignments) + if($objectType.PreReplaceCommand) { - if($assignment.target.UserId -or ($assignment.Source -and $assignment.Source -ne "direct")) + $ret = & $objectType.PreReplaceCommand $obj $objectType $curObject.Object $fileObj + if($ret -is [Hashtable]) { - # E.g. Source could be PolicySet...so should not be added here - continue - } + if($ret["Replace"] -eq $false) { $replace = $false } + + if($ret["Import"] -eq $false) { $import = $false } - $assignment.Id = "" - foreach($prop in $assignment.PSObject.Properties) + if($ret["Delete"] -eq $false) { $delete = $false } + } + } + + if($import) + { + $newObj = Import-GraphObject $obj $objectType $fileObj.FileInfo.FullName + } + + if($newObj -and $replace) + { + if($objectType.PostReplaceCommand) { - if($prop.Name -in $keepProperties) { continue } - Remove-Property $assignment $prop.Name + $ret = & $objectType.PostReplaceCommand $newObj $objectType $curObject.Object $fileObj + if($ret -is [Hashtable]) + { + if($ret["Delete"] -eq $false) { $delete = $false } + } } - foreach($prop in $assignment.target.PSObject.Properties) + # Load all information about current object to include assignments + $curObject = Get-GraphObject $curObject.Object $objectType + + $refAssignments = $curObject.Object.Assignments | Where { $_.Source -ne "direct" } + if($refAssignments) { - if($prop.Name -in $keepTargetProperties) { continue } - Remove-Property $assignment.target $prop.Name + foreach($refAssignment in $refAssignments) + { + if($refAssignment.Source -eq "policySets") + { + Update-EMPolicySetAssignment $refAssignment $curObject $newObj $objectType + } + } } - $ObjectAssignments += $assignment - } - - $objClone.Assignments = $ObjectAssignments - if(($objClone.Assignments | measure).Count -gt 0) - { - $json = "{ `"$((?? $file.ObjectType.AssignmentsType "assignments"))`": " - $strAssign = "$((Update-JsonForEnvironment ($objClone.Assignments | ConvertTo-Json -Depth 10)))" - # Array characters [ ] is not included if there is only one assignment - # Added them if they are missing - if($strAssign.Trim().StartsWith("[") -eq $false) - { - $strAssign = (" [ " + $strAssign + " ] ") - } - $json = ($json + $strAssign + "}") + Import-GraphObjectAssignment $newObj $objectType $curObject.Object.Assignments $fileObj.FileInfo.FullName -CopyAssignments | Out-Null - if($json) + if($delete) { - $objAssign = Invoke-GraphRequest $api -HttpMethod $method -Content $json + Remove-GraphObject $curObject.Object $objectType } } - - if($assignmentsProcessed -ne $true -and $file.ObjectType.PostImportAssignmentsCommand) + elseif($replace -eq $false) # Might not be 100% correct. Replace -eq $false probably means that the object was patched and not imported eg default enrollment restrictions etc. { - & $file.ObjectType.PostImportAssignmentsCommand $newObj $file.ObjectType $file.FileInfo.FullName $objAssign + Write-Log "Failed to import file for $($fileObj.Object.$nameProp) ($($objectType.Title))" 2 } - } - } - catch - { - Write-LogError "Failed to import file '$($file.FileInfo.Name)'" $_.Exception + return $true + } } + # No object to update. Import the file + return $false } +function Import-GraphObjectAssignment +{ + param($obj, $objectType, $assignments, $fromFile, [switch]$CopyAssignments) + + if(($assignments | measure).Count -eq 0) { return } + + $preConfig = $null + $clonedAssignments = $assignments | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + if($objectType.PreImportAssignmentsCommand) + { + $preConfig = & $objectType.PreImportAssignmentsCommand $obj $objectType $fromFile $clonedAssignments + } + + if($preConfig -isnot [Hashtable]) { $preConfig = @{} } + + if($preConfig["Import"] -eq $false) { return } # Assignment managed manually so skip further processing + + $api = ?? $preConfig["API"] "$($objectType.API)/$($newObj.Id)/assign" + + $method = ?? $preConfig["Method"] "POST" + + $keepProperties = ?? $objectType.AssignmentProperties @("target") + $keepTargetProperties = ?? $objectType.AssignmentTargetProperties @("@odata.type","groupId") + + $ObjectAssignments = @() + foreach($assignment in $clonedAssignments) + { + if(($assignment.target.UserId -and $CopyAssignments -ne $true) -or ($assignment.Source -and $assignment.Source -ne "direct")) + { + # E.g. Source could be PolicySet...so should not be added here + continue + } + + $assignment.Id = "" + foreach($prop in $assignment.PSObject.Properties) + { + if($prop.Name -in $keepProperties) { continue } + Remove-Property $assignment $prop.Name + } + + foreach($prop in $assignment.target.PSObject.Properties) + { + if($prop.Name -in $keepTargetProperties) { continue } + Remove-Property $assignment.target $prop.Name + } + + $ObjectAssignments += $assignment + } + + if($ObjectAssignments.Count -eq 0) { return } # No "Direct" assignments + + $htAssignments = @{} + $htAssignments.Add((?? $objectType.AssignmentsType "assignments"), @($ObjectAssignments)) + + $json = $htAssignments | ConvertTo-Json -Depth 10 + if($CopyAssignments -ne $true) + { + $json = Update-JsonForEnvironment $json + } + + $objAssign = Invoke-GraphRequest $api -HttpMethod $method -Content $json + + if($objectType.PostImportAssignmentsCommand) + { + & $objectType.PostImportAssignmentsCommand $obj $objectType $fromFile $objAssign + } + +} #endregion #region Migration Info @@ -1564,13 +1854,14 @@ function Get-GraphMigrationObjectsFromFile $migFileName = Get-GraphMigrationTableForImport if(-not $migFileName) { return } - $global:MigrationTableCache = @() - $migFileObj = ConvertFrom-Json (Get-Content $migFileName -Raw) # No need to translate migrated objects in the same environment as exported if($migFileObj.TenantId -eq $global:organization.Id) { return } + $global:MigrationTableCache = @() + $global:MigrationTableCacheId = $migFileObj.TenantId + Write-Status "Loading migration objects" if($global:chkImportAssignments.IsChecked -eq $true) @@ -1940,6 +2231,11 @@ function Import-GraphObject $newObj = (Invoke-GraphRequest -Url $strAPI -Content $json -HttpMethod $method @params) + if($newObj -and $method -eq "POST") + { + Write-Log "$($objectType.Title) object imported successfully with id: $($newObj.Id)" + } + if($newObj -and $objectType.PostImportCommand) { & $objectType.PostImportCommand $newObj $objectType $fromFile diff --git a/README.md b/README.md index 9c0243e..3785451 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,22 @@ The script also support dependencies e.g. an App Protection is depending on an A This PowerShell application is based on the foundation modules CloudAPIPowerShellManagement and Core. These modules manages UI, settings, logging etc. The functionality for the application is located in the extension modules. This makes it easy to add/remove features, views etc. Additional features will be added... -**Security note:** Since the scripts are not signed, a warning might be display when running it and files might be blocked. The script will unblock all files. This is to avoid issues that it fails to load the MSAL library etc. If there are any security concerns, the PowerShell code can be reviewed. The DLL files are downloaded from Microsoft repositories, see links below. These files can be downloaded and replaced. The DLL files *CAN* be removed but MSAL is a pre-requisite for login. The script will try to find the DLL in the Az or MSAL.PS module if not found in the script root directory. DLL files are included to reduce dependencies. +**Security note:** Since the scripts are not signed, a warning might be display when running it and files might be blocked. The script will unblock all files. This is to avoid issues that it fails to load the MSAL library etc. If there are any security concerns, the PowerShell code can be reviewed and the DLL files can be downloaded manually from Microsoft repositories, see links below. The DLL files *CAN* be removed but MSAL is a pre-requisite for authentication. The script will try to find the DLL in the Az or MSAL.PS module if not found in the script root directory. DLL files are included to reduce dependencies. + +## Starting the App + +Before starting the app: + +* The CMD files needs to be unblocked before the app can be started. The app can be started without it but Windows will prompt with a security warning. +* The script will unblock all other files + +Before logging on: + +* The app will use the Intune PowerShell Azure Enterprise Application by default but request all permissions required by the script. The will most likely cause a consent prompt since it uses more permission than the Intune module. Enable **Use Default Permissions** in Settings to only request the current permissions granted to the Enterprise App. + **Note:** Using default permission might reduce functionality e.g. permissions for one or more object types might be missing +* Enable **Get Tenant List** in Settings if accessing multiple environments with the same account. This might cause a Consent prompt + +Start the script by running **Start.cmd**, **Start-WithConsole.cmd** or **Start-IntuneManagement.ps1**. **Start-WithConsole.cmd** will leave the command prompt window open so you can see the log while running the app. ## Documentation @@ -18,6 +33,30 @@ This script has an extension that can document profiles and policies in Intune. See [Documentation](Documentation.md) for more information +## Import + +The script can import the exported json files in multiple ways. + +* **Always import:** The script will try to import the file. It will not check if it exists. + This is the default behavior +* **Skip if object exists:** The script will look if there is an existing object with the same name and type. It will not import the file if existing object is detected +* **Replace (Preview):** If a existing object is detected, the script will + * Import the file without assignments + * Copy assignments from the existing object + * Run PostReplace commands - Priority will be set for Enrollment Restrictions etc. + * Update PolicySets object(s) to use the new imported object (detected by policySet assignments) + * Delete the original object +* **Update (Experimental):** This will update the existing object. + Note: This is not fully implemented yet. It only works on a few object types + +**WARNING:** Use Replace with caution! Replace will delete the existing object after the imported object is updated but could cause issues in the environment if something in the process goes wrong. Verify the process in a test environment before using this! + +**Recommendation:** Backup all policies before running Replace/Update. + +The Replace/Update feature can be used in a scenario where all profiles/policies are managed in a separate reference (Dev/Test) and then implemented in one or more destination environment. The existing objects will then be reset to have the same settings as the reference environment + +**Note:** This must be turned on in Settings by enabling the **Allow update on import (Preview)** setting. + ## Comparison This script has an extension that can compare objects in Intune with exported json files. It will display a data grid with the values and highlight updated values with red. @@ -114,9 +153,9 @@ Some MSAL functionalities are based on [MSAL.PS Module](https://github.com/Azure ## Known Issues -Device Configuration and App Configuration objects are split up in different object types. They are using different Graph APIs and each object type in the menu uses one API. This is also why all Endpoint Security objects are of the same object type. They use the same API but are separated based on the Baseline Template Id they us. +Device Configuration and App Configuration objects are split up in different object types. They are using different Graph APIs and each object type in the menu uses one API. This is also why all Endpoint Security objects are of the same object type. They use the same API but are separated based on the Baseline Template Id they use. -Android Store Apps are **not** imported. The create method is documented in Microsoft Graph but it's not working. Looks like these apps must be synched from Google Play. +Android Store Apps are **not** imported. The Create API is documented in Microsoft Graph but it's not working. Looks like these apps must be synched from Google Play. Using multiple tenants support causes multiple logins/consent prompts the first time if 'Microsoft Graph PowerShell' is used. Querying the API for tenant list uses a different scope that is not included by default in the 'Microsoft Graph PowerShell' app. @@ -134,7 +173,7 @@ See [Documentation](Documentation.md) for issues regarding the documentation pro ## TIP -Check the log file for errors. The UI might not show errors why login failed etc. The log uses the Endpoint Configuration Manager (SCCM) format and it is best viewed with CMTrace. An old version can be downloaded [here](https://www.microsoft.com/en-us/download/confirmation.aspx?id=50012). +Check the log file for errors. The UI might not show errors why login failed etc. The log uses the Endpoint Configuration Manager (SCCM) format and it is best viewed with CMTrace or OneTrace. An old version of CMTrace can be downloaded [here](https://www.microsoft.com/en-us/download/confirmation.aspx?id=50012). ## License diff --git a/ReleaseNotes.md b/ReleaseNotes.md index a6cb666..f0de9f4 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,28 @@ # Release Notes +## 3.1.7 - 2021-07-12 + +**New features** + +- Support for documenting Notifications +- **PREVIEW/EXPERIMENTAL** - Support for Replace/Update existing profiles/policies during import. + See the Import section in the [Readme](Readme.md#Import) file for more information + This is based on the feature request in [Issue 17](https://github.com/Micke-K/IntuneManagement/issues/17) + +**Fixes** + +* Fixed bug that caused an exception when listing App Protection objects and only one object existed in the environment. + + See [Issue 15](https://github.com/Micke-K/IntuneManagement/issues/15) for more info + +* Import Priority based objects in the priority order specified in the files (Enrolment Restrictions and Autopilot profiles) + +* Set default settings for the options in the Import forms (Based on Settings) + +* Delete Autopilot profiles with assignments + +* Moved the assignments import to a separate function + ## 3.1.6 - 2021-07-07 **Fixes** diff --git a/Xaml/BulkImportForm.xaml b/Xaml/BulkImportForm.xaml index d9c420f..9d0a3ea 100644 --- a/Xaml/BulkImportForm.xaml +++ b/Xaml/BulkImportForm.xaml @@ -14,6 +14,7 @@ + @@ -68,9 +69,25 @@ + + + - + diff --git a/Xaml/ImportForm.xaml b/Xaml/ImportForm.xaml index 5eea87d..2fc7644 100644 --- a/Xaml/ImportForm.xaml +++ b/Xaml/ImportForm.xaml @@ -13,6 +13,7 @@ + @@ -58,10 +59,33 @@ - + @@ -79,8 +103,10 @@ - + + + + +