text:
{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang3081\deflangfe3081\themelang3081\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;}
{\f34\fbidi \froman\fcharset1\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\f36\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
{\f38\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Tahoma;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f399\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\f400\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}
{\f402\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\f403\fbidi \froman\fcharset162\fprq2 Cambria Tur;}{\f406\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}{\f409\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}
{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}
{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}
{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\caccentone\ctint255\cshade191\red54\green95\blue145;\chyperlink\ctint255\cshade255\red0\green0\blue255;}{\*\defchp
\f31506\fs22\lang3081\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sl276\slmult1
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033 \snext0 \sqformat \spriority0 \styrsid10893599 Normal;}{
\s1\ql \li0\ri0\sb480\sl276\slmult1\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af31503\afs28\alang1025 \ltrch\fcs0
\b\fs28\cf17\lang3081\langfe1033\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp3081\langfenp1033 \sbasedon0 \snext0 \slink15 \sqformat \spriority9 \styrsid1770717 heading 1;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1
Default Paragraph Font;}{\*\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv
\ql \li0\ri0\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033
\snext11 \ssemihidden \sunhideused \sqformat Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af31503\afs28 \ltrch\fcs0 \b\fs28\cf17\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink1 \slocked \spriority9 \styrsid1770717 Heading 1 Char;}{
\s16\ql \li720\ri0\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033
\sbasedon0 \snext16 \sqformat \spriority34 \styrsid1770717 List Paragraph;}{\s17\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af38\afs16\alang1025 \ltrch\fcs0
\f38\fs16\lang3081\langfe1033\cgrid\langnp3081\langfenp1033 \sbasedon0 \snext17 \slink18 \ssemihidden \sunhideused \styrsid2633113 Balloon Text;}{\*\cs18 \additive \rtlch\fcs1 \af38\afs16 \ltrch\fcs0 \f38\fs16
\sbasedon10 \slink17 \slocked \ssemihidden \styrsid2633113 Balloon Text Char;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf18 \sbasedon10 \sunhideused \styrsid2447344 Hyperlink;}}{\*\listtable{\list\listtemplateid-393812758\listhybrid{\listlevel
\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid201916433\'02\'00);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc4
\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916441\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2
\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916443\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0
\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916431\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4
\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916441\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2
\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916443\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0
\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916431\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4
\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916441\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2
\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid201916443\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname
;}\listid1269462706}}{\*\listoverridetable{\listoverride\listid1269462706\listoverridecount0\ls1}}{\*\rsidtbl \rsid1770717\rsid2447344\rsid2633113\rsid4076533\rsid9188832\rsid10893599\rsid14507565\rsid16005095\rsid16344712}{\mmathPr\mmathFont34\mbrkBin0
\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Scott Bass}{\operator Scott Bass}{\creatim\yr2013\mo3\dy12\hr9\min47}{\revtim\yr2013\mo3\dy12\hr9\min47}{\version2}{\edmins0}{\nofpages2}
{\nofwords508}{\nofchars2902}{\*\company BUPA Australia}{\nofcharsws3404}{\vern32773}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect
\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen
\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1
\jexpand\viewkind1\viewscale120\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct
\asianbrkrule\rsidroot9188832\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0
{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid10893599\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}
{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}
{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\s1\qc \li0\ri0\sb480\sl276\slmult1\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0\pararsid1770717 \rtlch\fcs1
\ab\af31503\afs28\alang1025 \ltrch\fcs0 \b\fs28\cf17\lang3081\langfe1033\loch\af31502\hich\af31502\dbch\af31501\cgrid\langnp3081\langfenp1033 {\rtlch\fcs1 \af31503 \ltrch\fcs0 \insrsid1770717 \hich\af31502\dbch\af31501\loch\f31502
Installation Instructions for SAS AMO Demo Spreadsheets}{\rtlch\fcs1 \af31503 \ltrch\fcs0 \insrsid10893599
\par }\pard\plain \ltrpar\ql \li0\ri0\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid1770717 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033 {
\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717
\par Below are installation instructions for the SAS AMO Demo Spreadsheets presented by Scott Bass at the 2013Q1 SNUG meeting.
\par
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid1770717 \hich\af31506\dbch\af31506\loch\f31506 1)\tab}}\pard\plain \ltrpar\s16\ql \fi-360\li720\ri0\sl276\slmult1
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls1\adjustright\rin0\lin720\itap0\pararsid1770717\contextualspace \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033 {\rtlch\fcs1 \af31507
\ltrch\fcs0 \insrsid1770717 Login to the SAS Management Console as an administrator user.  You need the rights to be able to import a SAS package file to create the stored processes used by the Excel workbooks.
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid1770717 \hich\af31506\dbch\af31506\loch\f31506 2)\tab}Decide which user you\rquote ll run the AMO demos with, for example sasdemo, or your own userid.
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid1770717 \hich\af31506\dbch\af31506\loch\f31506 3)\tab}Navigate to the \'93My Folder\'94 for the selected user.  For example, on my system:  \line SAS Folders }{
\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717 {\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717  User Folders }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717
{\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717  sbass }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717 {\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}
}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717  My Folder
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid1770717 \hich\af31506\dbch\af31506\loch\f31506 4)\tab}I recommend creating a sub-folder for these stored processes, for example \'93SNUG\'94.
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid1770717 \hich\af31506\dbch\af31506\loch\f31506 5)\tab}Import the attached }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113
SAS Package File (SPK) into that folder, i.e.\line SAS Folders }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113 {\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113  User Folders }{
\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113 {\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113  sbass }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113 {\field{\*\fldinst SYMBO
L 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113  My Folder }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113 {\field{\*\fldinst SYMBOL 224 \\f "Wingdings" \\s 11}{\fldrslt\f10\fs22}}}{\rtlch\fcs1
\af31507 \ltrch\fcs0 \insrsid2633113  SNUG.}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid1770717
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid2633113 \hich\af31506\dbch\af31506\loch\f31506 6)\tab}}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113
Verify the import by logging into the SAS Management Console with your demo user account.
\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af31507\afs22 \ltrch\fcs0 \f31506\fs22\insrsid2633113 \hich\af31506\dbch\af31506\loch\f31506 7)\tab}You should see something like:
\par }\pard\plain \ltrpar\ql \li0\ri0\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid2633113 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang3081\langfe1033\cgrid\langnp3081\langfenp1033 {
\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid2633113
\par }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \lang1024\langfe1024\noproof\langfenp3081\insrsid14507565\charrsid15077731 {\*\shppict{\pict{\*\picprop\shplid1025{\sp{\sn shapeType}{\sv 75}}{\sp{\sn fFlipH}{\sv 0}}
{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fRotateText}{\sv 1}}{\sp{\sn pictureGray}{\sv 0}}{\sp{\sn pictureBiLevel}{\sv 0}}{\sp{\sn fFilled}{\sv 0}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn wzName}{\sv Picture 1}}{\sp{\sn wzDescription}{\sv C:\'5c
DOCUME~1\'5csbass\'5cLOCALS~1\'5cTemp\'5cSNAGHTML4860733.PNG}}{\sp{\sn fHidden}{\sv 0}}{\sp{\sn fLayoutInCell}{\sv 1}}}\picscalex52\picscaley52\piccropl0\piccropr0\piccropt0\piccropb0
\picw30212\pich10423\picwgoal17128\pichgoal5909\pngblip\bliptag-1833773614{\*\blipuid 92b2d5d279bb974520da5d5c5ebf6593}89504e470d0a1a0a0000000d49484452000004760000018a08060000006959c645000000097048597300000ec400000ec401952b0e1b0000000774494d4507dd
[encoded data, 77696 characters]
02040810204080000102040810203043027beebefb5faecfd07c4c850001020408102040800001020408102040a0a1c0ff07e1a9fa0736bf96ae0000000049454e44ae426082}}{\nonshppict{\pict\picscalex53\picscaley53\piccropl0\piccropr0\piccropt0\piccropb0
\picw30212\pich10423\picwgoal17128\pichgoal5909\wmetafile8\bliptag-1833773614\blipupi96{\*\blipuid 92b2d5d279bb974520da5d5c5ebf6593}
[encoded data, 1871488 characters]
[cell cut: 5019710 characters, first 2000000 kept]


text:
/*=====================================================================
Program Name            : create_format.sas
Purpose                 : Create a format from an input dataset.
SAS Version             : SAS 9.1.3
Input Data              : Input Dataset
Output Data             : Formats

Macros Called           : parmv, kill, nobs

Originally Written by   : Scott Bass
Date                    : 23MAR2007
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

Programmer              : Scott Bass
Date                    : 30NOV2012
Change/reason           : Changed to use SPDEWORK library for better
                          performance
Program Version #       : 1.1

Programmer              : Scott Bass
Date                    : 13JUL2016
Change/reason           : Added KEEP parameter
Program Version #       : 1.2

=====================================================================*/

/*----------------------------------------------------------------------
Usage:

%create_format(
    DATA=WORK.FOO
   ,NAME=ABC_FMT
);

Create $ABC_FMT format in WORK.FORMATS catalog using WORK.FOO as input.
WORK.FOO should contain the variables START and LABEL.

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

%create_format(
    DATA=WORK.FOO
   ,NAME=DEF_FMT
   ,TYPE=NUM_FORMAT
   ,START=START_VALUE
   ,END=END_VALUE
   ,LABEL=DESCR
   ,LIB=LIBRARY
   ,CAT=FORMATS
)

Create DEF_NUM format in LIBRARY.FORMATS catalog using WORK.FOO as input.
WORK.FOO should contain the variables START_VALUE, END_VALUE, and DESCR.

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

%create_format(
    DATA=PERM.DATASET
   ,NAME=GHI_CHR
   ,TYPE=CHR_INFORMAT
   ,START=NAME
   ,LABEL=DESCR
   ,LIB=LIBRARY
   ,CAT=MYFORMAT
   ,OTHER=**UNKNOWN**
   ,WHERE=DATE gt "01JAN2007"d
)

Create GHI_CHR informat in LIBRARY.MYFORMAT catalog using PERM.DATASET as input.
Input data not matching any of the ranges should be coded to "**UNKNOWN**".
PERM.DATASET should contain the variables NAME and DESCR.
Only include records after 01JAN2007 in the format.

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

%create_format(
    DATA=WORK.FOO
   ,NAME=cats("MY_",FMT)
   ,TYPE=CHR_FORMAT
)

Create multiple formats from input dataset WORK.FOO.
Create the formats in the default catalog (WORK.FORMATS).
The name of the format is contained in the variable FMT.
We want the format names to begin with MY_.
WORK.FOO should contain the variables START and LABEL.

------------------------------------------------------------------------
Notes:

If the input dataset contains data for multiple formats
(i.e. it contains data for the name of the format), then the format
name string should contain a left-parentheses.

For example:

(VAR):
   Would just use VAR as is from the input dataset.
   The parentheses are just a trigger to the macro to treat this
   as a code fragment.

cats("FOO_",VAR):
   Would append "FOO_" to the VAR variable in the input dataset.

Otherwise &NAME will be used as a hard-coded format name for the entire
input dataset.

Combining a code fragment for the format name with Other processing
will result in only the last format containing Other processing.

No error checking is done on the input parameters (other than checking
if required parameters are set).

The variables for VALUE and LABEL should be character or automatic type
conversion will result.

Except for START and LABEL, your input dataset should avoid using
variable names listed in the attrib statement below.  For example, if
your input dataset contained the variable PREFIX, and you used it for
the VALUE or LABEL parameters, your data would likely be truncated
giving undesired results.

The macro issues a warning if your input dataset contains overlapping
ranges.  You should pre-process your input dataset to circumvent this
warning.
----------------------------------------------------------------------*/

%macro create_format
/*---------------------------------------------------------------------
Create a format from an input table
---------------------------------------------------------------------*/
(DATA=
               /* Input dataset/view (REQ).                          */
               /* Should be a two-level name.                        */
,LIB=WORK
               /* Format library (REQ).                              */
               /* Library to which the format is written.            */
,CAT=FORMATS
               /* Format catalog (REQ).                              */
               /* Catalog to which the format is written.            */
,NAME=
               /* Format name (REQ).                                 */
               /* Either a hard coded name, or a code fragment       */
               /* (that could reference a variable in the            */
               /* input dataset/view.                                */
,TYPE=CHR_FORMAT
               /* Format type (REQ).                                 */
               /* Valid values are CHR_FORMAT, NUM_FORMAT,           */
               /* CHR_INFORMAT, NUM_INFORMAT.                        */
,START=START
               /* Starting value for the format (REQ).               */
               /* Variable containing the start value for the format */
,END=
               /* Ending value for the format (Opt).                 */
               /* Variable containing the end value for the format   */
,LABEL=LABEL
               /* Format label (REQ).                                */
               /* Variable containing the label for the format.      */
,DEFAULT=
               /* Specify the default length of the (in)format.      */
               /* If blank, PROC FORMAT determines the default       */
               /* length based on the maximum label length.          */
,MIN=
               /* Specify a minimum length for the (in)format (Opt). */
,MAX=
               /* Specify a maximum length for the (in)format (Opt). */
,FUZZ=
               /* Specify a fuzz factor for matching values to a     */
               /* range (Opt).                                       */
,OTHER=
               /* "Other" processing? (Opt).                         */
               /* If non-blank, input data not matching any defined  */
               /* ranges will be mapped to the "other" label.        */
               /* If value = _MISSING_, value is translated to the   */
               /* appropriate label for the format type.             */
,KEEP=
               /* Additional variables to keep? (Opt)                */
               /* Used with where processing to keep additional      */
               /* variables used in the where claus.                 */
,WHERE=
               /* Where processing? (Opt).                           */
               /* If non-blank, only data matching the where clause  */
               /* will be included in the format.                    */
               /* Do not include the "where" keyword.                */
,DEDUP=Y
               /* Dedup the source dataset? (REQ).                   */
               /* Default value is YES.  Valid values are:           */
               /* 0 1 OFF N NO F FALSE and ON Y YES T TRUE           */
               /* OFF N NO F FALSE and ON Y YES T TRUE               */
               /* (case insensitive) are acceptable aliases for      */
               /* 0 and 1 respectively.                              */
);

%local macro parmerr keep;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(DATA,         _req=1,_words=1,_case=N)  /* words=1 allows ds options */
%parmv(LIB,          _req=1,_words=0,_case=U)
%parmv(CAT,          _req=1,_words=0,_case=U)
%parmv(NAME,         _req=1,_words=0,_case=U)
%parmv(TYPE,         _req=1,_words=0,_case=U,_val=CHR_FORMAT NUM_FORMAT CHR_INFORMAT NUM_INFORMAT)
%parmv(START,        _req=1,_words=0,_case=U)
%parmv(END,          _req=0,_words=0,_case=U)
%parmv(LABEL,        _req=1,_words=0,_case=U)
%parmv(DEFAULT,      _req=0,_words=0,_case=U,_val=POSITIVE)
%parmv(MIN,          _req=0,_words=0,_case=U,_val=POSITIVE)
%parmv(MAX,          _req=0,_words=0,_case=U,_val=POSITIVE)
%parmv(FUZZ,         _req=0,_words=0,_case=U)
%parmv(DEDUP,        _req=1,_words=0,_case=U,_val=0 1)

%if (&parmerr) %then %goto quit;

%let WHERE = %unquote(&WHERE);

libname _crtfmt_ spde "%sysfunc(pathname(work))" temp=yes;

%* I know I do not need all these attributes now, but I have left them in here ;
%* in case additional options are added to the macro in the future ;

%let cntlin=_crtfmt_._cntlin_;
data &cntlin;
   format FMTNAME TYPE START END LABEL;
   attrib
      FMTNAME     length=$32     label="Format name"
      START       length=$200    label="Starting value for format"
      END         length=$200    label="Ending value for format"
      LABEL       length=$5000   label="Format value label"
      TYPE        length=$1      label="Type of format"
/*
      MIN         length=3       label="Minimum length"
      MAX         length=3       label="Maximum length"
      DEFAULT     length=3       label="Default length"
      LENGTH      length=3       label="Format length"
      FUZZ        length=8       label="Fuzz value"
      PREFIX      length=$2      label="Prefix characters"
      MULT        length=8       label="Multiplier"
      FILL        length=$1      label="Fill character"
      NOEDIT      length=3       label="Is picture string noedit?"
      SEXCL       length=$1      label="Start exclusion"
      EEXCL       length=$1      label="End exclusion"
      HLO         length=$11     label="Additional information"
      DECSEP      length=$1      label="Decimal separator"
      DIGSEP      length=$1      label="Three-digit separator"
      DATATYPE    length=$8      label="Date/time/datetime?"
      LANGUAGE    length=$8      label="Language for date strings"
*/
   ;
   if _n_=1 then call missing(of _all_);

   %* if the format name or label contains a left parentheses, assume it is a code fragment ;
   %* otherwise it is a hard coded format name or label ;
   %if (%index(%superq(NAME),%str(%())) %then %do;
      %let NAME = &NAME;
      %let keep=&keep %sysfunc(compress(&NAME,%str(%(%))));
   %end;
   %else %do;
      %let NAME = "&NAME";
   %end;

   %if (&TYPE = CHR_FORMAT) %then
      %let type = C;
   %else
   %if (&TYPE = NUM_FORMAT) %then
      %let type = N;
   %else
   %if (&TYPE = CHR_INFORMAT) %then
      %let type = J;
   %else
   %if (&TYPE = NUM_INFORMAT) %then
      %let type = I;

   %let keep=&keep &START;
   %if (%superq(END) eq ) %then
      %let END = &START;
   %else
      %let keep=&keep &END;

   %let keep=&keep &LABEL;

   set &DATA (keep=&keep) end=_last_;

   %if (%superq(WHERE) ne %str() ) %then %do;
      where &WHERE;
   %end;

   FMTNAME  = &NAME;
   TYPE     = "&type";
   START    = &START;
   END      = &END;
   LABEL    = &LABEL;

   %if (&default ne ) %then %do;
   DEFAULT  = &default;
   %end;

   %if (&min ne ) %then %do;
   MIN      = &min;
   %end;

   %if (&max ne ) %then %do;
   MAX      = &max;
   %end;

   %if (&fuzz ne ) %then %do;
   FUZZ     = &fuzz;
   %end;

   output;

   %if (%superq(OTHER) ne ) %then %do;
      if (_last_) then do;
         call missing(of START, END, LABEL, &START, &END, &LABEL);

         %if (%qupcase(%superq(OTHER)) eq _MISSING_) %then %do;
            %if (%sysfunc(indexc(&TYPE,CNJ))) %then %do;
               LABEL = " ";
            %end;
            %else %do;
               LABEL = .;
            %end;
         %end;
         %else %do;
            %if (%sysfunc(indexc(&TYPE,CNJ))) %then %do;
               LABEL = "&OTHER";
            %end;
            %else %do;
               LABEL = &OTHER;
            %end;
         %end;

         HLO   = "O";
         output;
      end;
   %end;

   /*
   keep
      FMTNAME
      START
      END
      LABEL
      MIN
      MAX
      DEFAULT
      LENGTH
      FUZZ
      PREFIX
      MULT
      FILL
      NOEDIT
      TYPE
      SEXCL
      EEXCL
      HLO
      DECSEP
      DIGSEP
      DATATYPE
      LANGUAGE
   ;
   */
run;

%if (&dedup) %then %do;
  %let cntlin=_crtfmt_._cntlin_nodup_;

  %* remove duplicate ranges from input dataset ;
  proc sort data=_crtfmt_._cntlin_ out=&cntlin dupout=_crtfmt_._cntlin_dupout_ nodupkey;
     by fmtname start;
  run;

  %* print message if duplicate observations were deleted ;
  %if (%nobs(_crtfmt_._cntlin_dupout_) gt 0) %then %do;
     %* put %str(WAR)NING:  Duplicate ranges were detected in the &DATA dataset.;
     %put %str(NO)TE:  Duplicate ranges were detected in the &DATA dataset.;

     %* Uncomment the below line if calling this macro from a DIS job ;
     %*rcSet(4);
  %end;
%end;

%* create format(s) ;
proc format cntlin=&cntlin lib=&LIB..&CAT;
quit;

%quit:
%* if (&parmerr) %then %abort;

%mend;

/******* END OF FILE *******/

repo_name: scottbass/SAS

repo_language: SAS

mime_type: text/plain


text:
/*====================================================================
Program Name            : handle.sas
Purpose                 : Print a list of open file handles to the
                          SAS log
SAS Version             : SAS 9.2
Input Data              : None
Output Data             : None

Macros Called           : None

Originally Written by   : Scott Bass
Date                    : 29NOV2011
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    :

Programmer              : Scott Bass
Date                    : 03MAY2013
Change/reason           : Added unlock and email options to close
                          handles that are locking SAS datasets and
                          send and email to the process owner.
Program Version #       : 1.1

====================================================================*/

/*--------------------------------------------------------------------
Usage:

%handle;

Lists all open file handles on the server (probably not what you want)

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

Base Engine:

data one two;
   x=1;
run;

%handle(lib=work,           unlock=no, email=no);
%handle(data=work._all_,    unlock=no, email=no);
%handle(lib=work,data=one,  unlock=no, email=no);
%handle(lib=work,data=two,  unlock=no, email=no);
%handle(data=work.one,      unlock=no, email=no);
%handle(data=work.two,      unlock=no, email=no);

Will list nothing, since work.one and work.two are not locked.

Now open work.one via viewtable in another SAS session and repeat the above.

Now also open work.two via viewtable (so both one and two are open)
and repeat the above.

See log for results.

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

Repeat the above, specifying unlock=yes.

WARNING: This may have repercussions on your Base SAS DMS Session.

You can also try this using Enterprise Guide, which will only affect
your Workspace Server session.

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

SPDE Engine:

Open two datasets in claimdm via viewtable (eg. coverdim and datedim)

%handle(lib=claimdm);
%handle(data=claimdm._all_);
%handle(lib=claimdm,data=addressdim);
%handle(lib=claimdm,data=hcpcodesdim);
%handle(data=claimdm.addressdim);
%handle(data=claimdm.hcpcodesdim);

See log for results.

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

Explicit path specified:

Open any dataset in Lev2 meta, which uses a concatenated libref.
For example, meta.dmcolumndefinitions.

%handle(lib=meta,data=dmcolumndefinitions);
%handle(data=meta.dmcolumndefinitions);

These invocations should both fail since meta is a concatenated libref
in Lev2.

%handle(lib=Q:\tier0\test\meta);
%handle(lib=Q:\tier0\test\meta,data=dmcolumndefinitions);

This should work, see log for results.

----------------------------------------------------------------------
Notes:

The handle command does not accept usual filename wildcards, but will
filter the results based on a partial file fragment, including the file
path.  So, we can filter the results by specifying a path, a filename
(even a partial filename), or both.  See the help for handle.exe for
more details.

If LIB is specified, it will override the library portion if you also
specify a two-level DATA parameter.

If you do not specify the LIB parameter, best practice is to specify a
two-level DATA parameter.  Otherwise, you could get the results of a
like-named file locked in another library (eg. a Lev2 dataset instead of
a Lev1 dataset).

If the dataset name for a two-level DATA parameter is "_ALL_", this is
equivalent to specifying only the LIB parameter, and the open file
handles for all files in that library will be returned.

I have not bothered adding support for multiple datasets in the same
macro call.  So, if you need to check locks for multiple datasets,
either check the library (which may return more than you want), or call
the macro multiple times.

This macro with NOT work with concatenated libraries.  The library
allocation must point to a single path.  If you want to test for open
handles on a concatenated library, explicitly specify the path in the
LIB parameter.

--------------------------------------------------------------------*/

%macro handle
/*--------------------------------------------------------------------
Print a list of open file handles to the SAS log
--------------------------------------------------------------------*/
(LIB=          /* SAS Library (Opt).                                */
               /* If not specified, the DATA parameter is checked   */
               /* for a two-level dataset name.  If so, that library*/
               /* is used.                                          */
,DATA=         /* SAS Dataset (Opt).                                */
               /* Either a one-level or two-level name can be       */
               /* specified.                                        */
,UNLOCK=NO     /* Kill the SAS process locking the dataset? (Opt).  */
               /* The default value is NO.                          */
               /* OFF N NO F FALSE and ON Y YES T TRUE              */
               /* (case insensitive) are acceptable aliases for     */
               /* 0 and 1 respectively.                             */
,EMAIL=NO      /* Send email to SAS process owner? (Opt.)           */
               /* The default value is NO.                          */
               /* OFF N NO F FALSE and ON Y YES T TRUE              */
               /* (case insensitive) are acceptable aliases for     */
               /* 0 and 1 respectively.                             */
);

%local macro parmerr _lib _data path;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(UNLOCK,       _req=0,_words=0,_case=U,_val=0 1)
%parmv(EMAIL,        _req=0,_words=0,_case=U,_val=0 1)

%if (&parmerr) %then %goto quit;

%global handle_rc;
%let handle_rc=0;

%* set various defaults ;
%let handle       = E:\SAS\Config\Utilities\handle64.exe;
%let handle_opts  = -accepteula -u;

%* options for email message ;
%let to=John.Doe@acme.com;  %* default email address, will get replaced if %useridToEmail lookup succeeds ;
%let cc=John.Doe@acme.com ^ Billy.Bob@acme.com ^ Suzie.Que@acme.com;
%* let cc=John.Doe@acme.com;  %* for development ;
%let subject=Your SAS session was locking Acme Datamart Datasets;
%let email_path=E:\SAS\Config\%sysget(lev)\SASApp\SASEnvironment\SASMacro\handle_email.txt;

%* Set library and dataset ;
%let _lib= %scan(&data,1,.);
%let _data=%scan(&data,2,.);

%* One-level name was specified ;
%if (&_data eq ) %then %do;
   %let _data=&_lib;
   %let _lib=work;
%end;

%* The library in a two-level dataset name takes precedence over the lib parameter, ;
%if (&_lib eq ) %then %let _lib=&lib;

%if (%upcase(&_data) eq _ALL_) %then %let _data=;

%* Now set the path filter ;
%if (%superq(_lib) ne ) %then %do;
   %* if an explicit path was specified, use the path as is ;
   %* assume an explict path contains at least one backslash ;
   %if (%index(%superq(_lib),\)) %then %do;
      %* nothing ;
   %end;
   %else %do;
      %* assume it is a libref ;

      %* is the library allocated ;
      %if (%sysfunc(libref(%superq(_lib))) ne 0) %then %do;
         %put ERROR:: Libref &_lib is not allocated.;
         %goto quit;
      %end;
      %else %do;
         %let _lib=%sysfunc(pathname(%superq(_lib)));

         %* if the library is a concatenated path print a message and quit ;
         %if (%index(%superq(_lib),%str(%())) %then %do;
            %put NOTE:: The handle macro does not work with concatenated library allocations.;
            %goto quit;
         %end;
      %end;
   %end;
   %* remove trailing slash ;
   %if (%substr(%superq(_lib),%length(%superq(_lib))) eq \) %then %let _lib=%substr(%superq(_lib),1,%length(%superq(_lib))-1);
%end;

%* Set path ;
%if (%superq(_lib) ne ) %then
   %let path=&_lib\&_data;
%else
   %let path=&_data;
%if (%superq(path) eq ) %then
   %let path=\;

%* Now invoke handle via filename pipe and echo the results to the log ;
%put;
%put Invoking Handle Macro:;
%put Current PID: &sysjobid;
%put;

/*
The below regular expression means:
^               = beginning of line
(.+?) pid: +    = one or more of any character (non-greedy matching (ngm)), until <space>pid:<one or more spaces>
pid: +(\d+) +   = pid:<one or more spaces>, one or more digits, <one or more spaces>
(\d+) +type: +  = one or more digits, <one or more spaces>, type:, <one or more spaces>
type: +(.+?) +  = type:, <one or more spaces>, one or more of any character (ngm), <one or more spaces>
(.+?)\\(.+?)    = one or more of any character (ngm), backslash, one or more of any character (ngm)
                  (some system userids have a space in them, for example NT AUTHORITY\LOCAL SERVICE
(  |>)          = either two spaces or a greater than sign (either delimits the end of the userid)
(.+?):          = one or more of any character (ngm), followed by a colon (:) and <space>
(.+)            = one or more of any character (greedy matching), up to ...
$               = end of line
*/

%let options=%sysfunc(getoption(ls,keyword));
options ls=max;
data _processes_;
   length process $30 pid 5 type $8 domain $15 userid $20 handle $5 path $300;
   if _n_=1 then do;
      rx=prxparse("/^(.+?) pid: +(\d+) +type: +(.+?) +(.+?)\\(.+?)(  |>)(.+?): (.+)$/o");
      retain rx;
   end;
   infile "&handle &handle_opts &path" pipe lrecl=1024 firstobs=6;
   input;
   if prxmatch(rx,trim(_infile_)) then do;
      process   = strip(prxposn(rx,1,_infile_));
      pid       = input(strip(prxposn(rx,2,_infile_)),best.);
      type      = strip(prxposn(rx,3,_infile_));
      domain    = strip(prxposn(rx,4,_infile_));
      userid    = strip(prxposn(rx,5,_infile_))||strip(prxposn(rx,6,_infile_));
      handle    = strip(prxposn(rx,7,_infile_));
      path      = strip(prxposn(rx,8,_infile_));

      %* print data to SAS log ;
      retain header 0;
      if (not header) then link header;
      temp=catx("\",domain,userid);
      putlog   @1 process   @33 pid 5. @40 type   @49 temp            @85 path;
      return;

      %* print header ;
      header:
         header=1;
         putlog;
         putlog @1 "WARNING: One or more files are locked.";
         putlog;
         putlog @1 "PROCESS" @33 "PID" @40 "TYPE" @49 "DOMAIN\USERID" @85 "PATH";
         putlog @1 "=======" @33 "===" @40 "====" @49 "=============" @85 "====";
      return;
   end;
   else delete;
   drop rx temp header;
run;
options &options;

%if (%nobs(_processes_) eq 0) %then %goto quit;

%* if unlock=yes, use handle again to unlock the handles ;
%* use systask instead of system(), call system(), or X since it causes less window flashing ;
%if (&unlock) %then %do;
   *** close open file handles *** ;
   data _null_;
      set _processes_;
      length command $500;
      command=catx(
         " ",
         "&handle",
         "-accepteula",
         "-p", pid,
         "-c", handle,
         "-y"
      );
      command=catx(
         " ",
         "systask",
         "command",
         quote(strip(command)),
         "wait;"
      );
      call execute(command);
   run;
%end;

*** check that the file handles have been closed *** ;
data _null_;
   call symputx("handle_rc",0);
   infile "&handle &handle_opts &path" pipe lrecl=1024 firstobs=6;
   input;
   if (prxmatch("/No matching handles found./o",_infile_)) then do;
      putlog _infile_/"0A0D"x;
      stop;
   end;
   call symputx("handle_rc",1);
   stop;
run;

%* if email=yes, send an email to each unique userid ;
%if (&email) %then %do;
   *** send notification email *** ;
   proc sql noprint;
      select distinct
         catx("|",userid,pid) into :userids separated by "^"
      from
         _processes_
      where
         domain="INTERNAL"  /* only email "human" userids, not service accounts */
      ;
   quit;

   %macro handleSendEmail;
      %let userid    = %scan(&word,1,|);
      %let pid       = %scan(&word,2,|);

      %* initialize variables used in the email text ;
      %let firstname = &userid;  %* initialize in case %useridToEmail lookup fails ;

      %* Retrieve email address from userid ;
      %useridToEmail(userid=&userid,mvar=emailaddress)

      %if (&emailaddress ne ) %then %do;
         %let to=&emailaddress;
         %* assume email address is in the form firstname.lastname@acme.com ;
         %let firstname=%scan(&emailaddress,1,.);
         %let firstname=%sysfunc(propcase(&firstname));
      %end;

      %* put email lookup debugging information in the log ;
      %put qad_rc=&qad_rc;
      %put emailaddress=&emailaddress;

      %* create mail dataset from external file ;
      data mail;
         length parm $15 line $1000;
         retain parm "";
         retain header 0;

         do until (eof1);
            infile "&email_path" truncover lrecl=1000 end=eof1;
            input;
            line=resolve(_infile_);
            output;
         end;

         %* add appendix information to the email ;
         do until (eof2);
            set _processes_ end=eof2;
            if (not header) then link header;
            call missing(line);
            substr(line,1) =process;
            substr(line,33)=put(pid,5.);
            substr(line,40)=type;
            substr(line,49)=catx("\",domain,userid);
            substr(line,85)=path;
            output;
         end;

         stop;

         header:
            header=1;
            call missing(line);
            line="Appendix: List of locked files";
            output;
            call missing(line);
            output;
            substr(line,1) ="PROCESS";
            substr(line,33)="PID";
            substr(line,40)="TYPE";
            substr(line,49)="DOMAIN\USERID";
            substr(line,85)="PATH";
            output;
            call missing(line);
            substr(line,1) ="=======";
            substr(line,33)="===";
            substr(line,40)="====";
            substr(line,49)="=============";
            substr(line,85)="====";
            output;
         return;

         keep parm line;
      run;

      %sendmail(
         to=&to
         ,cc=&cc
         ,subject=&subject
         ,content_type=text/plain
         ,metadata=mail
      )
   %mend;
   %loop(&userids,mname=handleSendEmail,dlm=^)
%end;

%quit:

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : marker.sas
Purpose                 : Process marker files used to control job
                          scheduling
SAS Version             : SAS 9.2
Input Data              : N/A
Output Data             : N/A

Macros Called           : parmv

Originally Written by   : Scott Bass
Date                    : 11NOV2011
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

%marker(path=C:\Temp\Stage1.mkr,action=create);
%put rc=&rc;

Create empty marker file C:\Temp\Stage1.mkr

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

%marker(path=C:\Temp\Stage1.mkr,action=check,sleep=10,timeout=30);
%put rc=&rc;

Checks for marker file C:\Temp\Stage1.mkr
  Sleeping for 10 seconds before checking again
  Timing out after 30 seconds

Sets the macro variable &rc to 0 if the marker file is created
within the timeout period, otherwise &rc is set to 1.

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

%macro executeCode;
   %marker(path=C:\Temp\Stage1.mkr,action=check,_rc=marker_rc);
   %if (&marker_rc eq 0) %then %do;
      %* processing if marker file exists ;
      %put MARKER FILE EXISTS;
   %end;
   %else %do;
      %* processing if marker file does not exist within the timeout period ;
      %put MARKER FILE DOES NOT EXIST;
   %end;
%mend;
%executeCode;

Same as above
   Within the context of a macro
   Using the default values for sleep and timeout periods
   Creating the &marker_rc status macro variable

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

%marker(path=C:\Temp\*.mkr,action=delete,_rc=delete_rc);
%put delete_rc=&delete_rc;

Delete all marker files named *.mkr in directory C:\Temp

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

%marker(path=C:\Temp\Stage1.mkr,action=check,check=notexists);
%put rc=&rc;

Checks that the marker file C:\Temp\Stage1.mkr does NOT exist
   Using the default values for sleep and timeout periods
   Returning success if the file is deleted within the timeout period

Sets the macro variable &rc to 0 if the marker file does not exist
within the timeout period, otherwise &rc is set to 1.

-----------------------------------------------------------------------
Notes:

Marker files are empty files used by scheduling software as a condition
for executing downstream job(s).

The condition can be either the existence or non-existence of the file,
depending on the upstream job processing.

It is the *existence* (or non-existence) of the file, not the contents
of the file itself, that serves as the trigger for the scheduling
software.

---------------------------------------------------------------------*/

%macro marker
/*---------------------------------------------------------------------
Process marker files used to control job scheduling.
---------------------------------------------------------------------*/
(PATH=         /* Full path to marker file (REQ).                    */
,ACTION=       /* Action to take (REQ).                              */
               /* Valid values are CHECK, CREATE, and DELETE.        */
,CHECK=EXISTS  /* Specifies whether to check for the existence or    */
               /* non-existence of the marker file (REQ).            */
               /* This parameter is ignored unless ACTION=CHECK.     */
               /* Valid values are EXISTS and NOTEXISTS.             */
,SLEEP=300     /* Period in seconds to sleep while checking for the  */
               /* existence or non-existence of the marker file (REQ)*/
               /* Default value is 5 minutes (300 seconds).          */
               /* Specify 0 to immediately fail if the check for the */
               /* marker file fails.                                 */
               /* This parameter is ignored unless ACTION=CHECK.     */
,TIMEOUT=3600  /* Period in seconds to wait for the marker file      */
               /* check to succeed before timing out (REQ).          */
               /* This parameter is ignored unless ACTION=CHECK.     */
,_RC=RC        /* Macro variable which contains the return code (REQ)*/
               /* Returned as a global macro variable.               */
               /* Default value is &rc.                              */
);

%local macro parmerr marker check now end temp;
%global &_rc;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(PATH,         _req=1,_words=1)  /* path could contain spaces */
%parmv(ACTION,       _req=1,_words=0,_case=U,_val=CHECK CREATE DELETE)
%parmv(CHECK,        _req=1,_words=0,_case=U,_val=EXISTS NOTEXISTS)
%parmv(SLEEP,        _req=1,_words=0,_case=U,_val=NONNEGATIVE)
%parmv(TIMEOUT,      _req=1,_words=0,_case=U,_val=NONNEGATIVE)
%parmv(_RC,          _req=1,_words=0,_case=U)

%if (&parmerr) %then %goto quit;

%if (&action eq CHECK) %then %do;
   %let now=%sysfunc(datetime());
   %let end=%sysevalf(&now + &timeout);
   %let marker=0;
   %if (&check eq EXISTS) %then
      %let check=1;
   %else
   %if (&check eq NOTEXISTS) %then
      %let check=0;

   %do %until (%sysevalf(&now ge &end));
      %let marker=%sysfunc(fileexist(&path));
      %if (&marker eq &check) %then %goto marker;
      %put %sysfunc(putn(&now,datetime.)): Sleeping for &sleep seconds...;
      %let temp=%sysfunc(sleep(&sleep,1));
      %let now=%sysfunc(datetime());
   %end;

%marker:
   %let &_rc=%eval(&marker ne &check);  %* returns 0 if success, 1 if timeout ;
%end;

%else
%if (&action eq CREATE) %then %do;
   systask command "type NUL > ""&path"" " wait status=&_rc taskname="Create Marker File";
%end;

%else
%if (&action eq DELETE) %then %do;
   systask command "del ""&path"" " wait status=&_rc taskname="Delete Marker File";
%end;

%quit:

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : varexist.sas
Purpose                 : Check for the existence of a specified
                          variable, optionally returning attributes.
SAS Version             : Unknown (probably SAS 8.2)
Input Data              : N/A
Output Data             : N/A

Macros Called           : parmv

Originally Written by   : Scott Bass
Date                    : 27APR2007
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

* Test data ;
data test;
   length dummy1 dummy2 $1 date 5 dummy4 dummy5 8;
   attrib date
      format=date9.
      informat=yymmdd8.
      label="This is a date variable"
   ;
   call missing(of _all_);
   date="01JAN1960"d;
run;

* Variable exists ;
%put EXISTS: %varexist(work.test,date);  * returns varnum, i.e. 3 ;

* Variable does not exist ;
%put EXISTS: %varexist(work.test,doesnotexist);  * returns 0 ;

* Dataset does not exist ;
%put EXISTS: %varexist(doesnotexist,doesnotexist);  * returns 0 ;

* Variable attributes ;
%put VARNUM:   %varexist(work.test,date,num);
%put LENGTH:   %varexist(work.test,date,len);
%put FORMAT:   %varexist(work.test,date,fmt);
%put INFORMAT: %varexist(work.test,date,infmt);
%put LABEL:    %varexist(work.test,date,label);
%put TYPE:     %varexist(work.test,date,type);

-----------------------------------------------------------------------
Notes:

This macro must be used in a context valid for a function call.

It returns an RVALUE, so it must be assigned to a variable,
i.e. be called on the right hand side of an equals sign.

Code copied from various postings to comp.soft-sys.sas.
---------------------------------------------------------------------*/

%macro varexist
/*---------------------------------------------------------------------
Check for the existence of a specified variable, optionally returning
attributes.
---------------------------------------------------------------------*/
(DATA          /* Source dataset (REQ).                              */
,VAR           /* Source variable (REQ).                             */
,INFO          /* Variable attribute (Opt).                          */
               /* If blank, a flag variable is returned indicating   */
               /* if the variable exists.                            */
               /* If specified, the variable attribute is returned.  */
               /* Valid values are:                                  */
               /*    NUM   = variable number                         */
               /*    LEN   = length of variable                      */
               /*    FMT   = format of variable                      */
               /*    INFMT = informat of variable                    */
               /*    LABEL = label of variable                       */
               /*    TYPE  = type of variable (N=numeric,C=character)*/
               /* Default is to return the variable number           */
);

%local macro parmerr dsis rc varnum;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(DATA,         _req=1,_words=0)
%parmv(VAR,          _req=1,_words=0)
%parmv(INFO,         _req=0,_words=0,_case=U,_val=NUM LEN FMT INFMT LABEL TYPE)

%if (&parmerr) %then %goto quit;

%*---------------------------------------------------------------------
Use the SYSFUNC macro to execute the SCL OPEN, VARNUM,
other variable information and CLOSE functions.
----------------------------------------------------------------------;
%let dsid = %sysfunc(open(&data));

%if (&dsid) %then %do;
   %let varnum = %sysfunc(varnum(&dsid,&var));

   %if (&varnum) %then %do;
      %if (%length(&info)) %then %do;
         %if (&info eq NUM) %then %do;
&varnum
         %end;
         %else %do;
%sysfunc(var&info(&dsid,&varnum))
         %end;
      %end;
      %else %do;
&varnum
      %end;
   %end;

   %else 0;

   %let rc = %sysfunc(close(&dsid));
%end;

%else 0;

%quit:
%mend;

/******* END OF FILE *******/


text:
/*====================================================================
Program Name            : hash_split_dataset.sas
Purpose                 : Use hash object to split dataset into
                          multiple datasets
SAS Version             : SAS 9.1.3
Input Data              : SAS input dataset
Output Data             : Multiple SAS output datasets

Macros Called           : parmv, kill

Originally Written by   : Scott Bass
Date                    : 12MAY2010
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

+===================================================================*/

/*--------------------------------------------------------------------
Usage:

data source;
   length key1 svar1 $1;
   input  key1 svar1;
   datalines;
A  1
B  2
C  3
1  A
2  B
2  C
3  D
3  E
3  F
;
run;

proc format;
   value $key
      "1"   = "FOO"
      "2"   = "BAR"
      "3"   = "BLAH"
   ;
run;

----------------------------------------------------------------------

%hash_split_dataset(data=source, out=%str(cats("OUT_",key1)), by=key1);

data source2 / view=source2;
   set source (where=(key1 between "1" and "3"));
run;

%m_hash_split_dataset(data=source2, out=%str(put(key1,$key.)), by=key1, vars=);

This will result in the output datasets OUT_A, OUT_B, OUT_C, OUT_1, OUT_2, OUT_3
and FOO, BAR, and BLAH respectively.

----------------------------------------------------------------------
Notes:

The source dataset must be sorted or indexed on the byvar.

The OUT= parameter must be a SAS code fragment that generates the
desired dynamic SAS output dataset name.  Typically this would be
cats("Prefix",key_var), put could also be something like
put(key_var,some_format.) or cats("Prefix",put(key_var,some_format.))

--------------------------------------------------------------------*/

%macro hash_split_dataset
/*--------------------------------------------------------------------
Use hash object to split dataset into multiple datasets
--------------------------------------------------------------------*/
(DATA=         /* Source dataset (REQ).                             */
,OUT=          /* SAS code fragment that defines the dynamic, data  */
               /* driven output dataset names (REQ).                */
,BY=           /* Grouping variable for output dataset observations */
               /* (REQ).  Only a single variable can be specified.  */
,VARS=_ALL_    /* Desired output variables (Opt).                   */
               /* Default is all variables in the source dataset.   */
               /* Analogous to keep statement on the output dataset.*/
               /* If set to blank only the by variable is output.   */
);

%local macro parmerr;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(DATA,         _req=1,_words=0,_case=U)
%parmv(OUT,          _req=1,_words=1,_case=N)
%parmv(BY,           _req=1,_words=0,_case=U)
%parmv(VARS,         _req=0,_words=1,_case=U)

%if (&parmerr) %then %goto quit;

%* if VARS=_ALL_, need to get an explicit list of all the dataset variables ;
%* proc contents is much faster than dictionary.columns when a large # of libraries are allocated ;
%if (&vars eq _ALL_) %then %do;
   proc contents data=&data out=_contents_ (keep=name varnum) noprint nodetails;
   run;
   proc sql noprint;
      select name into :vars separated by " "
      from _contents_
      where upcase(name) ne "%upcase(&by)"
      order by varnum;
      drop table _contents_;
   quit;
%end;

%* create sorted view of input dataset ;
proc sql noprint;
   create view _sorted_ as
      select * from &data order by &by
   ;
quit;

%* use a hash object to split a dataset based on the grouping variable ;
data _null_;
   %* declare the hash object ;
   declare hash h (hashexp: 16, ordered: "A");

   %* define keys and satellite variables ;
   h.defineKey("&by","_n_");
   h.defineData(%seplist(&by &vars,nest=QQ));

   %* end hash declaration ;
   h.defineDone();

   do _n_=1 by 1 until (last.&by);
      set _sorted_;
      by &by;
      h.add();
   end;
   h.output(dataset: &out);
run;

%kill(delete=_sorted_)

%quit:
%* if (&parmerr) %then %abort;

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : IsNumM.sas
Purpose                 : Checks if alphanumeric MACRO variable input
                          (which is always character data) is valid
                          numeric data.
                          Optionally checks for IsInt, IsNonNeg, or
                          IsPos.
SAS Version             : SAS 9.3
Input Data              : Alphanumeric data contained in a macro
                          variable.
Output Data             : Return code indicating IsNum, IsInt,
                          IsNonNeg, or IsPos.

Macros Called           : parmv

Originally Written by   : Scott Bass
Date                    : 16FEB2016
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

options missing=' ';

* check for numeric input ;
%put isnum=%IsNumM( );
%put isnum=%IsNumM(.);
%put isnum=%IsNumM(._);
%put isnum=%IsNumM(.A);
%put isnum=%IsNumM(.Z);
%put isnum=%IsNumM(-1);
%put isnum=%IsNumM(0);
%put isnum=%IsNumM(1);
%put isnum=%IsNumM(1.1);
%put isnum=%IsNumM( 123456789012.123456789012);
%put isnum=%IsNumM(-123456789012.123456789012);
%put isnum=%IsNumM( 1234567890123.1234567890123);
%put isnum=%IsNumM(-1234567890123.1234567890123);
%put isnum=%IsNumM( 12345678901234.12345678901234);
%put isnum=%IsNumM(-12345678901234.12345678901234);
%put isnum=%IsNumM( 123456789012345.123456789012345);
%put isnum=%IsNumM(-123456789012345.123456789012345);
%put isnum=%IsNumM( 1234567890123456.1234567890123456);
%put isnum=%IsNumM(-1234567890123456.1234567890123456);
%put isnum=%IsNumM( 1.1);
%put isnum=%IsNumM( -123);
%put isnum=%IsNumM( -123.45);
%put isnum=%IsNumM( --123.45);
%put isnum=%IsNumM(A);
%put isnum=%IsNumM( B);
%put isnum=%IsNumM(123 456);
%put isnum=%IsNumM(123-456);
%put isnum=%IsNumM(~!@#$%^&*()_+=);

options missing='.';
%put isnum=%IsNumM( );
%put isnum=%IsNumM(.);
%put isnum=%IsNumM(._);
%put isnum=%IsNumM(.A);
%put isnum=%IsNumM(.Z);
%put isnum=%IsNumM(-1);
%put isnum=%IsNumM(0);
%put isnum=%IsNumM(1);
%put isnum=%IsNumM(1.1);
%put isnum=%IsNumM( 123456789012.123456789012);
%put isnum=%IsNumM(-123456789012.123456789012);
%put isnum=%IsNumM( 1234567890123.1234567890123);
%put isnum=%IsNumM(-1234567890123.1234567890123);
%put isnum=%IsNumM( 12345678901234.12345678901234);
%put isnum=%IsNumM(-12345678901234.12345678901234);
%put isnum=%IsNumM( 123456789012345.123456789012345);
%put isnum=%IsNumM(-123456789012345.123456789012345);
%put isnum=%IsNumM( 1234567890123456.1234567890123456);
%put isnum=%IsNumM(-1234567890123456.1234567890123456);
%put isnum=%IsNumM( 1.1);
%put isnum=%IsNumM( -123);
%put isnum=%IsNumM( -123.45);
%put isnum=%IsNumM( --123.45);
%put isnum=%IsNumM(A);
%put isnum=%IsNumM( B);
%put isnum=%IsNumM(123 456);
%put isnum=%IsNumM(123-456);
%put isnum=%IsNumM(~!@#$%^&*()_+=);

* check for integer input ;
%put isint=%IsNumM(1,type=int);
%put isint=%IsNumM(1.1,type=int);
%put isint=%IsNumM(999999999999,type=int);
%put isint=%IsNumM(9999999999999,type=int);
%put isint=%IsNumM(99999999999999,type=int);
%put isint=%IsNumM(999999999999999,type=int);
%put isint=%IsNumM(9999999999999999,type=int);
%put isint=%IsNumM(99999999999999999,type=int);
%put isint=%IsNumM(999999999999999999,type=int);
%put isint=%IsNumM(9999999999999999999,type=int);

* check for non-negative input ;
%put isnonneg=%IsNumM(-1,type=nonneg);
%put isnonneg=%IsNumM(-1.2,type=nonneg);
%put isnonneg=%IsNumM(0,type=nonneg);
%put isnonneg=%IsNumM(1,type=nonneg);
%put isnonneg=%IsNumM(1.2,type=nonneg);

* check for positive input ;
%put ispos=%IsNumM(-1,type=pos);
%put ispos=%IsNumM(-1.2,type=pos);
%put ispos=%IsNumM(0,type=pos);
%put ispos=%IsNumM(1,type=pos);
%put ispos=%IsNumM(1.2,type=pos);

* treat missing data (not including special missing data) as valid ;
%put missing=%IsNumM(  ,missing=N);
%put missing=%IsNumM(. ,missing=N);
%put missing=%IsNumM(._,missing=N);
%put missing=%IsNumM(.A,missing=N);
%put missing=%IsNumM( .M,missing=N);
%put missing=%IsNumM(  .Z  ,missing=N);
%put missing=%IsNumM(-1,missing=N);
%put missing=%IsNumM( 0,missing=N);
%put missing=%IsNumM( 1,missing=N);

%put missing=%IsNumM(  ,missing=Y);
%put missing=%IsNumM(. ,missing=Y);
%put missing=%IsNumM(._,missing=Y);
%put missing=%IsNumM(.A,missing=Y);
%put missing=%IsNumM( .M,missing=Y);
%put missing=%IsNumM(  .Z  ,missing=Y);
%put missing=%IsNumM(-1,missing=Y);
%put missing=%IsNumM( 0,missing=Y);
%put missing=%IsNumM( 1,missing=Y);

-----------------------------------------------------------------------
Notes:

This is "pure macro" code and returns an R-value ("function style macro").
It must be called in the correct context for such a macro.

Since %sysfunc does not support the INPUT function, the INPUTN function
is used instead.  INPUTN does not support the ?? modifier to suppress
invalid numeric input.

Therefore, additional error checking of the
source data (via a Perl regular expression) is used to prevent the
macro from throwing warning messages.

Note that the check for IsInt collapses for large numbers,
where precision is lost and the difference between
num and int(num) becomes 0 [num-int(num)=0].

On my machine, this happens around 1E15, or 15 significant digits.

Unlike the %IsNum macro, which runs in a data step, this function style
macro does not treat special missing values as valid numeric data.
This is an edge case that I am happy not to cater for.

---------------------------------------------------------------------*/

%macro IsNumM
/*---------------------------------------------------------------------
Checks if alphanumeric (character) input is valid numeric data.
---------------------------------------------------------------------*/
(CHAR          /* Character variable to check (Opt).                 */
 ,TYPE=NUM     /* Type of check to run (REQ).                        */
               /* Valid values are NUM INT NONNEG or POS             */
               /* (case-insensitive)                                 */
 ,MISSING=N    /* Include missing values as valid numeric input?(REQ)*/
               /* Default value is NO.  Valid values are:           */
               /* 0 1 OFF N NO F FALSE and ON Y YES T TRUE          */
               /* OFF N NO F FALSE and ON Y YES T TRUE              */
               /* (case insensitive) are acceptable aliases for     */
               /* 0 and 1 respectively.                             */
);

%local macro parmerr rx match num;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(TYPE,         _req=1,_words=0,_case=U,_val=NUM INT NONNEG POS)
%parmv(MISSING,      _req=1,_words=0,_case=U,_val=0 1)

%if (&parmerr) %then %goto quit;

%* remove leading spaces ;
%let char=&char;
%* put CHAR=*&char*;  %* for debugging ;

%* use a Perl regex to test for empty string, missing(.), or special missing (._,.A-.Z) data  ;
%* if YES: ;
%* if MISSING=N treat as not valid numeric data ;
%* if MISSING=Y treat as valid numeric data ;
%let rx = %sysfunc(prxparse(/^( *|\.[A-Z_]*)$/o));
%let match = %sysfunc(prxmatch(&rx,%superq(char)));
%syscall prxfree(rx);
%if (&match eq 1) %then %do;
   %if (&missing) %then %let rtn=1; %else %let rtn=0;
&rtn
   %return;
%end;

%* use a Perl regex to test for numeric input ;
%* the regex is "/^(-{0,1})(\d*)(\.{0,1})(\d*)$/o", which means: ;
%* beginning of string (^): ;
%* 0 or 1 minus signs: ;
%* 0 or more digits: ;
%* 0 or 1 periods: ;
%* 0 or more digits: ;
%* end of string ($) ;
%* compile the regex once (o) ;
%let rx = %sysfunc(prxparse(/^(-{0,1})(\d*)(\.{0,1})(\d*)$/o));
%let match = %sysfunc(prxmatch(&rx,%superq(char)));
%syscall prxfree(rx);

%* if no match then not num ;
%if (&match eq 0) %then %do;
0
   %return;
%end;

%* the Perl regex should be sufficient to cleanse the input to the inputn function ;
%* convert the value to num. if it is missing then not num ;
%* note: this only supports options missing='.' and options missing=' ' ;
%let num=%sysfunc(inputn(%superq(char),best32.),best32.);
%let num=#
%* put NUM =#&num#; %* for debugging ;
%if (%superq(num) eq .) or (%superq(num) eq ) %then %do;
0
   %return;
%end;

%* it is probably a num :-) ;
%if (&type eq NUM) %then %do;
1
%end;
%else
%if (&type eq INT) %then %do;
   %let int=%sysfunc(int(&num),32.);
   %put INT =#&int#;
   %let rtn=%eval(&int eq &num);
&rtn
%end;
%else
%if (&type eq NONNEG) %then %do;
   %let rtn=%sysevalf(&num ge 0);
&rtn
%end;
%else
%if (&type eq POS) %then %do;
   %let rtn=%sysevalf(&num gt 0);
&rtn
%end;

%quit:

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : libname_sqlsvr.sas
Purpose                 : Allocate a SQL Server library via ODBC
SAS Version             : SAS 9.3
Input Data              : N/A
Output Data             : N/A

Macros Called           : parmv
                          dump_mvars

Originally Written by   : Scott Bass
Date                    : 12AUG2016
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    :

Programmer              : Scott Bass
Date                    : 09MAR2017
Change/reason           : Changed options parameter to augment the
                          internal options rather than completely
                          override them.  This change is to more easily
                          support access=readonly libname option.
Program Version #       : 1.1

Programmer              : Scott Bass
Date                    : 19SEP2018
Change/reason           : Added PRESERVE_NAMES=YES option since we
                          have now migrated to case-sensitive
                          collation on SQL Server.
Program Version #       : 1.2

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

%libname_sqlsvr(libref=RLDXHosp)

Allocates the libref RLDXHosp,
with defaults of database=<libref>, schema=dbo, and options as coded
in the macro.

Note that this only works if the database name is a valid SAS libref,
i.e. less than 8 characters.

The macro will select the correct DEV or PROD database based on the
METAPORT option (i.e. which profile is active in EG)

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

%libname_sqlsvr(libref=mylib, database=RLDXHosp_prod)

Allocates the libref MYLIB,
with the explicit database=RLDXHosp_prod, schema=dbo, and options as
coded in the macro.

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

%libname_sqlsvr(libref=TMP, database=RLDXHosp_dev, schema=tmp)

Allocates the libref TMP,
with the explicit database=RLDXHosp_dev, schema=tmp, and options as
coded in the macro.

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

%libname_sqlsvr(
   libref=RLDXHosp,
   options=
      bulkload=yes
      schema=dbo
      reread_exposure=yes
      DBINDEX=YES
      UPDATE_LOCK_TYPE=NOLOCK
      IGNORE_READ_ONLY_COLUMNS=YES
)

Allocates the libref RLDXHosp,
with defaults of database=<libref>, and additional options that
augment the internal options coded in the macro.

Note that the explicit options should augment the internal options
coded in the macro.  If any of the options match the internal options,
the invocation options should override the internal options since
"last option wins".

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

%libname_sqlsvr(libref=RLDXHosp, server=FOO, port=12345)

Allocates the libref RLDXHosp,
with defaults of database=<libref>, schema=dbo,
and server=FOO, port=12345.

-----------------------------------------------------------------------
Notes:

The macro sets these internal macro variables:

%let lev = %sysfunc(ifc(%sysfunc(getoption(METAPORT)) eq 8561,Lev1,Lev2));
%let env = %sysfunc(ifc(%sysfunc(getoption(METAPORT)) eq 8561,prod,dev));

So, if METAPORT=8561 then lev=Lev1 and env=prod.
Otherwise, lev=Lev2 and env=dev.

If the DATABASE parameter is blank, then database parameter will
default to &libref._&env.

Otherwise, the DATABASE parameter will be used, irrespective of the
EG environment.  IOW, you can allocate a production database from
Lev2 by explicitly specifying the database.

The default SCHEMA is dbo.

Currently only Trusted Connection is supported and is hard coded.

---------------------------------------------------------------------*/

%macro libname_sqlsvr
/*---------------------------------------------------------------------
Allocate a SQL Server library via ODBC
---------------------------------------------------------------------*/
(LIBREF=       /* Libref to allocate (REQ)                           */
,DATABASE=     /* Database to use (Opt).                             */
               /* If blank then LIBREF_<env> is used.                */
,SCHEMA=dbo    /* Default database schema (REQ).  Default is dbo.    */
,OPTIONS=      /* Libref options (Opt).  If specified, the options   */
               /* completely override (as opposed to augment) the    */
               /* options embedded in this macro.                    */
,SERVER=SVDCMHPRRLSQD01
               /* SQL Server machine name (REQ).                     */
,PORT=         /* SQL Server machine port (REQ).                     */
               /* Note: the port number for DOHNSCLDBSASBI is 54491  */

);

%local macro parmerr lev env _server;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(LIBREF,       _req=1,_words=0,_case=N)
%parmv(DATABASE,     _req=0,_words=0,_case=N)
%parmv(SCHEMA,       _req=1,_words=0,_case=N)
%parmv(OPTIONS,      _req=0,_words=1,_case=N)
%parmv(SERVER,       _req=1,_words=0,_case=N)
%parmv(PORT,         _req=0,_words=0,_case=N,_val=POSITIVE)

%if (&parmerr) %then %goto quit;

%* set the correct environment ;
%let lev = %sysfunc(ifc(%sysfunc(getoption(METAPORT)) eq 8561,Lev1,Lev2));
%let env = %sysfunc(ifc(%sysfunc(getoption(METAPORT)) eq 8561,prod,dev));

%* If the database was not specified, set it to the <libref>_<env> ;
%if (&DATABASE eq ) %then %let database=&libref._&env;

%* set the connection string ;
%let _server=&SERVER;
%if (&PORT ne ) %then %let _server=&_server,&PORT;

%let connect = NOPROMPT="Driver={SQL Server Native Client 10.0};Server=&_server;Database=&database;Trusted_Connection=yes;";
/* %let connect = NOPROMPT="Driver={ODBC Driver 11 for SQL Server};Server=&_server;Database=&database;Trusted_Connection=yes;"; */

%* set any internal hardcoded libname options ;
%*let internal_options = bulkload=yes schema=&schema insertbuff=100 readbuff=1000 direct_exe=delete connection=global autocommit=yes dbcommit=50;
%*let internal_options = bulkload=yes schema=&schema reread_exposure=yes DBINDEX=YES UPDATE_LOCK_TYPE=NOLOCK IGNORE_READ_ONLY_COLUMNS=YES;
%*let internal_options = bulkload=yes schema=&schema reread_exposure=yes DBINDEX=YES UPDATE_LOCK_TYPE=NOLOCK;
%*let internal_options = bulkload=yes schema=&schema insertbuff=100 readbuff=1000 direct_exe=delete connection=global;
%*let internal_options = bulkload=yes schema=&schema direct_exe=delete connection=global;
%*let internal_options = bulkload=yes schema=&schema connection=global;
%let internal_options = schema=&schema bulkload=yes dbcommit=100000 direct_exe=delete preserve_names=yes;
%*let internal_options = schema=&schema;

%* issue the libname statement ;
%put %sysfunc(repeat(=,80));
%put LIBREF:           %upcase(&libref);
%put CONNECT:          &connect;
%put INTERNAL OPTIONS: %sysfunc(compbl(%superq(internal_options)));
%put USER OPTIONS:     %sysfunc(compbl(%superq(options)));
%put %sysfunc(repeat(=,80));
%put;
libname &libref odbc &connect &internal_options &options;

%quit:

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : age.sas
Purpose                 : Determines a person's age in <units>
                          based on a reference date
SAS Version             : SAS 8.2
Input Data              : N/A
Output Data             : N/A

Macros Called           : parmv

Originally Written by   : Scott Bass
Date                    : 30MAY2006
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

data _null_;
   dob = "25Dec60"d;
   end = "25Dec05"d;

   age1 = %age(dob);                   * age in years from today() ;
   age2 = %age(dob,end);               * age in years from 25Dec05 ;
   age3 = %age(dob,end,units=month);   * age in months from 25Dec05 ;
   age4 = %age(dob,units=day);         * age in days from today() ;

   put (dob end) (=date7. +1) (age1-age4) (=);
run;

-----------------------------------------------------------------------
Notes:

This macro must be used in a context valid for a function call,
eg. inside a data step.

It returns an RVALUE, so it must be assigned to a variable,
i.e. be called on the right hand side of an equals sign.

Code copied from various postings to comp.soft-sys.sas.
---------------------------------------------------------------------*/

%macro age
/*---------------------------------------------------------------------
Determines a person's age in <units> (default = years)
based on a reference date.
---------------------------------------------------------------------*/
(BEGDATE       /* Beginning date (REQ).                              */
               /* Usually a person's DOB.                            */
,ENDDATE       /* End date (Opt).                                    */
               /* If not specified, TODAY() is used.                 */
,UNITS=YEAR    /* Units (REQ).                                       */
               /* Default is year.  Valid values are                 */
               /* Y YEAR M MONTH D DAY.                              */
);

%local macro parmerr enddate;
%let macro = &sysmacroname;

%* set default end date if it was not specified ;
%if (%superq(enddate) = %str( )) %then %let enddate = %sysfunc(today());

%* check input parameters ;
%parmv(BEGDATE,      _req=1,_words=0)
%parmv(ENDDATE,      _req=1,_words=0)
%parmv(UNITS,        _req=1,_words=0,_case=U,_val=Y YEAR M MONTH D DAY)

%if (&parmerr) %then %goto quit;

%if (%upcase(%substr(&units,1,1)) = Y) %then %do;
   floor((intck('month',&begdate,&enddate) - (day(&enddate)<day(&begdate)))/12)
%end;
%else
%if (%upcase(%substr(&units,1,1)) = M) %then %do;
   floor((intck('month',&begdate,&enddate) - (day(&enddate)<day(&begdate))))
%end;
%else
%if (%upcase(%substr(&units,1,1)) = D) %then %do;
   &enddate - &begdate
%end;

%quit:
%* if (&parmerr) %then %abort;

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : export_spss.sas
Purpose                 : Wrapper macro for the %export_dbms macro
                          used to export a SAS dataset to an SPSS file.
SAS Version             : SAS 9.4
Input Data              : SAS dataset
Output Data             : SPSS flat file

Macros Called           : export_dbms, parmv

Originally Written by   : Scott Bass
Date                    : 04SEP2019
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    : Original version

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

See Usage in the %export_dbms macro header.

-----------------------------------------------------------------------
Notes:

See Notes in the %export_dbms macro header.

---------------------------------------------------------------------*/

%macro export_spss
/*---------------------------------------------------------------------
Wrapper macro for the %export_dbms macro
used to export a SAS dataset to an SPSS file.
---------------------------------------------------------------------*/
(DATA=         /* Dataset to export (REQ).                           */
               /* Data set options, such as a where clause, may be   */
               /* specified.                                         */
,PATH=         /* Output directory or file path (REQ).               */
               /* Either a properly quoted physical file             */
               /* (single or double quotes), or an already allocated */
               /* fileref may be specified.                          */
,REPLACE=N     /* Replace output file? (Opt).                        */
               /* Default value is NO.  Valid values are:            */
               /* 0 1 OFF N NO F FALSE and ON Y YES T TRUE           */
               /* OFF N NO F FALSE and ON Y YES T TRUE               */
               /* (case insensitive) are acceptable aliases for      */
               /* 0 and 1 respectively.                              */
,LABEL=N       /* Use data set labels instead of names? (Opt).       */
               /* Default value is NO.  Valid values are:            */
               /* 0 1 OFF N NO F FALSE and ON Y YES T TRUE           */
               /* OFF N NO F FALSE and ON Y YES T TRUE               */
               /* (case insensitive) are acceptable aliases for      */
               /* 0 and 1 respectively.                              */
               /* If the data set variable does not have a label     */
               /* then the variable name is used.                    */
);

%local macro parmerr;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(DATA,         _req=1,_words=1,_case=N)  /* words=1 allows multiple datasets */
%parmv(PATH,         _req=1,_words=1,_case=N)
%parmv(REPLACE,      _req=0,_words=0,_case=U,_val=0 1)
%parmv(LABEL,        _req=0,_words=0,_case=U,_val=0 1)

%if (&parmerr) %then %return;

%* call the %export_dbms macro with the correct parameters ;
%* all further error trapping is done by the %export_dbms macro ;
%export_dbms(
   data=%superq(data)
   ,path=%superq(path)
   ,dbms=spss
   ,replace=%superq(replace)
   ,label=%superq(label)
)

%mend;

/******* END OF FILE *******/


text:
/*=====================================================================
Program Name            : loop.sas
Purpose                 : A "wrapper" macro to execute code over a
                          list of items
SAS Version             : SAS 8.2
Input Data              : N/A
Output Data             : N/A

Macros Called           : parmv

Originally Written by   : Scott Bass
Date                    : 24APR2006
Program Version #       : 1.0

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

Scott Bass (sas_l_739@yahoo.com.au)

This code is licensed under the Unlicense license.
For more information, please refer to http://unlicense.org/UNLICENSE.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

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

Modification History    :

Programmer              : Scott Bass
Date                    : 17JUL2006
Change/reason           : Added DLM parameter
Program Version #       : 1.1

Programmer              : Scott Bass
Date                    : 24DEC2009
Change/reason           : Made changes as suggested by Ian Whitlock,
                          updated header with additional usage cases
Program Version #       : 1.2

Programmer              : Scott Bass
Date                    : 13MAY2011
Change/reason           : Fixed nested macro scoping errors by explicitly
                          declaring the iterator and word as local.
Program Version #       : 1.3

=====================================================================*/

/*---------------------------------------------------------------------
Usage:

%macro code;
   %put &word;
%mend;
%loop(Hello World);

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

%let str = Hello,World;
%loop(%bquote(&str),dlm=%bquote(,));

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

%macro code();
   %put &word;
%mend;
%loop(Hello World,mname=code());

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

%macro mymacro;
   proc print data=&word;
   run;
%mend;

proc datasets kill nowarn nolist;
quit;

data one;x=1;run;
data two;y=2;run;

proc sql noprint;
   select memname into :list separated by '|'
      from dictionary.tables
      where libname = "WORK" and memtype = "DATA"
   ;
quit;
%loop(&list,dlm=|,mname=mymacro);

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

Calling a macro with parameters:

%macro mymacro(parm=&word);
   %put &parm;
%mend;
%loop(hello world,mname=mymacro);    * this causes a tokenization error ;
%loop(hello world,mname=mymacro());  * no error ;
%mymacro(parm=hi);

Note that the parm is the literal text '&word' (without quotes of course).
See additional details in Nested calls of %loop use case below.

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

Nested calls of %loop:

%macro outer;
   %put &sysmacroname &__iter__ &word;
   %loop(INNER_1 INNER_2 INNER_3,mname=inner)
%mend;

%let iter_global=;

%macro inner;
   %local iter_local;
   %global iter_global;

   %if (&iter_local  eq ) %then %let iter_local=1;
   %if (&iter_global eq ) %then %let iter_global=1;

   %let iter_local=%eval(&iter_local + (&__iter__ * 5));  %* not retained across outer loops ;
   %let iter_global=%eval((&iter_global*2)+(&__iter__));  %* retained across outer loops since it is global ;

   %put &sysmacroname &__iter__ &word iter_local=&iter_local iter_global=&iter_global;
   %* let __iter__=999;  %* if uncommented, this would cause a logic error ;
%mend;

%loop(OUTER_A OUTER_B OUTER_C OUTER_D OUTER_E,mname=outer)

Do NOT reset __iter__ in any inner macro or it will affect the outer macro.
You can *REFERENCE* &__iter__, but do not *CHANGE* its value.
If you need to make logic decisions, copy &__iter__ to a local inner variable.

Also, do NOT use %yourmacro(parameter=&word) syntax in nested calls to
%loop.  For example, this will not work:

options mlogic;

%macro level1(firstvar=&word);
   %put firstvar in Level1:  &firstvar;
   %loop(Variable2, mname=level2())
%mend level1;

%macro level2(secondvar=&word);
   %put secondvar in level2: &secondvar;
   %put INCORRECT: firstvar in Level2: &firstvar;
%mend level2;

%loop(Variable1, mname=level1())

In level1, firstvar is actually assigned the literal text '&word'
(without quotes of course). Inside the level1 macro, this text then
resolves to the value of &word AT THAT TIME. Once the level2 macro
executes, it changes the value of &word, which then changes further
references to &firstvar.

Instead, assign firstvar and secondvar to the value of &word INSIDE
each macro, as described for __iter__ above.

Closely review the mlogic output above for more details.

Instead, code this as follows:

options nomlogic;

%macro level1;
   %local firstvar;
   %let firstvar=&word;
   %put &sysmacroname: firstvar in Level1:  &firstvar;
   %loop(Variable4 Variable5, mname=level2)
%mend level1;

%macro level2;
   %local secondvar;
   %let secondvar=&word;
   %put &sysmacroname: secondvar in level2: &secondvar;
   %put &sysmacroname: CORRECT: firstvar in Level2: &firstvar;
%mend level2;

%loop(Variable1 Variable2 Variable3, mname=level1)

-----------------------------------------------------------------------
Notes:

The nested macro "%code" must be created at run time before calling
this macro.

Use the macro variable "&word" within your %code macro for each token
(word) in the input list.

If your macro has any parameters (named or keyword) (even an empty
list), then specify the macro name with empty parentheses for the mname
parameter or tokenization errors will occur during macro execution. For
example, %loop(hello world,mname=mymacro());.  See examples in the
Usage section above.

If your input list has embedded blanks, specify a different dlm value.

Do not use the prefix __ (two underscores) for any macro variables in
the child macro.  This prefix is reserved for any macro variables in
this looping macro.

To "carry forward" the value of the iterator across loops, assign it
(&__iter__) to a global macro variable in an inner macro.

---------------------------------------------------------------------*/

%macro loop
/*---------------------------------------------------------------------
Invoke the nested macro "%code" over a list of space separated
list of items.
---------------------------------------------------------------------*/
(__LIST__      /* Space or character separated list of items (REQ)   */
,DLM=%str( )   /* Delimiter character (REQ).  Default is a space.    */
,MNAME=code    /* Macro name (Optional).  Default is "%code"         */
);

%local macro parmerr __iter__ word;
%let macro = &sysmacroname;

%* check input parameters ;
%parmv(MNAME,        _req=1,_words=0,_case=N)

%if (&parmerr) %then %goto quit;

%* the iterator MUST be unique between macro invocations ;
%* if not, nested invocations of this macro cause looping problems ;
%* unfortunately, SAS macro does not support truly private variable scoping ;

%let __iter__ = 1;
%let word = %qscan(%superq(__list__),&__iter__,%superq(dlm));
%do %while (%superq(word) ne %str());
  %let word=%unquote(&word);
%&mname  /* do not indent macro call */
   %let __iter__ = %eval(&__iter__+1);
   %let word = %qscan(%superq(__list__),&__iter__,%superq(dlm));
%end;

%quit:
%* if (&parmerr) %then %abort;

%mend;

/******* END OF FILE *******/