diff --git a/all.sas b/all.sas
index 74591ad..106d858 100644
--- a/all.sas
+++ b/all.sas
@@ -10838,7 +10838,314 @@ options noquotelenmax;
libname &libref1 clear;
%end;
%mend;/**
- @file mv_createwebservice.sas
+ @file
+ @brief Creates a Viya Job
+ @details
+ Code is passed in as one or more filerefs.
+
+ %* Step 1 - compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* Step 2 - Create some SAS code and add it to a job;
+ filename ft15f001 temp;
+ parmcards4;
+ data some_code;
+ set sashelp.class;
+ run;
+ ;;;;
+ %mv_createjob(path=/Public/app/sasjstemp/jobs/myjobs,name=myjob)
+
+ The path to the job will then be shown in the log, eg as follows:
+
+ 
+
+
+
SAS Macros
+ @li mp_abort.sas
+ @li mv_createfolder.sas
+ @li mf_getuniquelibref.sas
+ @li mf_getuniquefileref.sas
+ @li mf_getplatform.sas
+ @li mf_isblank.sas
+ @li mv_deletejes.sas
+
+ @param path= The full path (on SAS Drive) where the job will be created
+ @param name= The name of the job
+ @param desc= The description of the job
+ @param precode= Space separated list of filerefs, pointing to the code that
+ needs to be attached to the beginning of the job
+ @param code= Fileref(s) of the actual code to be added
+ @param access_token_var= The global macro variable to contain the access token
+ @param grant_type= valid values are "password" or "authorization_code" (unquoted).
+ The default is authorization_code.
+ @param replace= select NO to avoid replacing any existing job in that location
+ @param contextname= Choose a specific context on which to run the Job. Leave
+ blank to use the default context. From Viya 3.5 it is possible to configure
+ a shared context - see
+ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
+
+ @version VIYA V.03.04
+ @author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
+
+**/
+
+%macro mv_createjob(path=
+ ,name=
+ ,desc=Created by the mv_createjob.sas macro
+ ,precode=
+ ,code=ft15f001
+ ,access_token_var=ACCESS_TOKEN
+ ,grant_type=sas_services
+ ,replace=YES
+ ,debug=0
+ ,contextname=
+ );
+%local oauth_bearer;
+%if &grant_type=detect %then %do;
+ %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
+ %else %let grant_type=sas_services;
+%end;
+%if &grant_type=sas_services %then %do;
+ %let oauth_bearer=oauth_bearer=sas_services;
+ %let &access_token_var=;
+%end;
+%put &sysmacroname: grant_type=&grant_type;
+
+/* initial validation checking */
+%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
+ and &grant_type ne sas_services
+ )
+ ,mac=&sysmacroname
+ ,msg=%str(Invalid value for grant_type: &grant_type)
+)
+%mp_abort(iftrue=(%mf_isblank(&path)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(path value must be provided)
+)
+%mp_abort(iftrue=(%length(&path)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(path value must be provided)
+)
+%mp_abort(iftrue=(%mf_isblank(&name)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(name value must be provided)
+)
+
+options noquotelenmax;
+
+* remove any trailing slash ;
+%if "%substr(&path,%length(&path),1)" = "/" %then
+ %let path=%substr(&path,1,%length(&path)-1);
+
+/* ensure folder exists */
+%put &sysmacroname: Path &path being checked / created;
+%mv_createfolder(path=&path)
+
+%local base_uri; /* location of rest apis */
+%let base_uri=%mf_getplatform(VIYARESTAPI);
+
+/* fetching folder details for provided path */
+%local fname1;
+%let fname1=%mf_getuniquefileref();
+proc http method='GET' out=&fname1 &oauth_bearer
+ url="&base_uri/folders/folders/@item?path=&path";
+%if &grant_type=authorization_code %then %do;
+ headers "Authorization"="Bearer &&&access_token_var";
+%end;
+run;
+%if &debug %then %do;
+ data _null_;
+ infile &fname1;
+ input;
+ putlog _infile_;
+ run;
+%end;
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+/* path exists. Grab follow on link to check members */
+%local libref1;
+%let libref1=%mf_getuniquelibref();
+libname &libref1 JSON fileref=&fname1;
+
+data _null_;
+ set &libref1..links;
+ if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l');
+ else if rel='self' then call symputx('parentFolderUri',href,'l');
+run;
+data _null_;
+ set &libref1..root;
+ call symputx('folderid',id,'l');
+run;
+%local fname2;
+%let fname2=%mf_getuniquefileref();
+proc http method='GET'
+ out=&fname2
+ &oauth_bearer
+ url=%unquote(%superq(membercheck));
+ headers
+ %if &grant_type=authorization_code %then %do;
+ "Authorization"="Bearer &&&access_token_var"
+ %end;
+ 'Accept'='application/vnd.sas.collection+json'
+ 'Accept-Language'='string';
+%if &debug=1 %then %do;
+ debug level = 3;
+%end;
+run;
+/*data _null_;infile &fname2;input;putlog _infile_;run;*/
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+%if %upcase(&replace)=YES %then %do;
+ %mv_deletejes(path=&path, name=&name)
+%end;
+%else %do;
+ /* check that job does not already exist in that folder */
+ %local libref2;
+ %let libref2=%mf_getuniquelibref();
+ libname &libref2 JSON fileref=&fname2;
+ %local exists; %let exists=0;
+ data _null_;
+ set &libref2..items;
+ if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then
+ call symputx('exists',1,'l');
+ run;
+ %mp_abort(iftrue=(&exists=1)
+ ,mac=&sysmacroname
+ ,msg=%str(Job &name already exists in &path)
+ )
+ libname &libref2 clear;
+%end;
+
+/* set up the body of the request to create the service */
+%local fname3;
+%let fname3=%mf_getuniquefileref();
+data _null_;
+ file &fname3 TERMSTR=' ';
+ length string $32767;
+ string=cats('{"version": 0,"name":"'
+ ,"&name"
+ ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"'
+ ,',"type":"CHARACTER","defaultValue":"false"}');
+ context=quote(cats(symget('contextname')));
+ if context ne '""' then do;
+ string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":'
+ ,context,',"type":"CHARACTER","label":"Context Name","required": false}');
+ end;
+ string=cats(string,'],"code":"');
+ put string;
+run;
+
+
+/* insert the code, escaping double quotes and carriage returns */
+%local x fref freflist;
+%let freflist= &precode &code ;
+%do x=1 %to %sysfunc(countw(&freflist));
+ %let fref=%scan(&freflist,&x);
+ %put &sysmacroname: adding &fref;
+ data _null_;
+ length filein 8 fileid 8;
+ filein = fopen("&fref","I",1,"B");
+ fileid = fopen("&fname3","A",1,"B");
+ rec = "20"x;
+ do while(fread(filein)=0);
+ rc = fget(filein,rec,1);
+ if rec='"' then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'"');rc =fwrite(fileid);
+ end;
+ else if rec='0A'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'r');rc =fwrite(fileid);
+ end;
+ else if rec='0D'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'n');rc =fwrite(fileid);
+ end;
+ else if rec='09'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'t');rc =fwrite(fileid);
+ end;
+ else if rec='5C'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ end;
+ else do;
+ rc =fput(fileid,rec);
+ rc =fwrite(fileid);
+ end;
+ end;
+ rc=fclose(filein);
+ rc=fclose(fileid);
+ run;
+%end;
+
+/* finish off the body of the code file loaded to JES */
+data _null_;
+ file &fname3 mod TERMSTR=' ';
+ put '"}';
+run;
+
+/* now we can create the job!! */
+%local fname4;
+%let fname4=%mf_getuniquefileref();
+proc http method='POST'
+ in=&fname3
+ out=&fname4
+ &oauth_bearer
+ url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri";
+ headers 'Content-Type'='application/vnd.sas.job.definition+json'
+ %if &grant_type=authorization_code %then %do;
+ "Authorization"="Bearer &&&access_token_var"
+ %end;
+ "Accept"="application/vnd.sas.job.definition+json";
+%if &debug=1 %then %do;
+ debug level = 3;
+%end;
+run;
+/*data _null_;infile &fname4;input;putlog _infile_;run;*/
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+/* clear refs */
+filename &fname1 clear;
+filename &fname2 clear;
+filename &fname3 clear;
+filename &fname4 clear;
+libname &libref1 clear;
+
+/* get the url so we can give a helpful log message */
+%local url;
+data _null_;
+ if symexist('_baseurl') then do;
+ url=symget('_baseurl');
+ if subpad(url,length(url)-9,9)='SASStudio'
+ then url=substr(url,1,length(url)-11);
+ else url="&systcpiphostname";
+ end;
+ else url="&systcpiphostname";
+ call symputx('url',url);
+run;
+
+
+%put &sysmacroname: Job &name successfully created in &path;
+%put &sysmacroname:;
+%put &sysmacroname: Check it out here:;
+%put &sysmacroname:;%put;
+%put &url/SASJobExecution?_PROGRAM=&path/&name;%put;
+%put &sysmacroname:;
+%put &sysmacroname:;
+
+%mend;
+/**
+ @file
@brief Creates a JobExecution web service if it doesn't already exist
@details
Code is passed in as one or more filerefs.
diff --git a/viya/mv_createjob.sas b/viya/mv_createjob.sas
new file mode 100644
index 0000000..e3eba3a
--- /dev/null
+++ b/viya/mv_createjob.sas
@@ -0,0 +1,307 @@
+/**
+ @file
+ @brief Creates a Viya Job
+ @details
+ Code is passed in as one or more filerefs.
+
+ %* Step 1 - compile macros ;
+ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
+ %inc mc;
+
+ %* Step 2 - Create some SAS code and add it to a job;
+ filename ft15f001 temp;
+ parmcards4;
+ data some_code;
+ set sashelp.class;
+ run;
+ ;;;;
+ %mv_createjob(path=/Public/app/sasjstemp/jobs/myjobs,name=myjob)
+
+ The path to the job will then be shown in the log, eg as follows:
+
+ 
+
+
+ SAS Macros
+ @li mp_abort.sas
+ @li mv_createfolder.sas
+ @li mf_getuniquelibref.sas
+ @li mf_getuniquefileref.sas
+ @li mf_getplatform.sas
+ @li mf_isblank.sas
+ @li mv_deletejes.sas
+
+ @param path= The full path (on SAS Drive) where the job will be created
+ @param name= The name of the job
+ @param desc= The description of the job
+ @param precode= Space separated list of filerefs, pointing to the code that
+ needs to be attached to the beginning of the job
+ @param code= Fileref(s) of the actual code to be added
+ @param access_token_var= The global macro variable to contain the access token
+ @param grant_type= valid values are "password" or "authorization_code" (unquoted).
+ The default is authorization_code.
+ @param replace= select NO to avoid replacing any existing job in that location
+ @param contextname= Choose a specific context on which to run the Job. Leave
+ blank to use the default context. From Viya 3.5 it is possible to configure
+ a shared context - see
+ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
+
+ @version VIYA V.03.04
+ @author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
+
+**/
+
+%macro mv_createjob(path=
+ ,name=
+ ,desc=Created by the mv_createjob.sas macro
+ ,precode=
+ ,code=ft15f001
+ ,access_token_var=ACCESS_TOKEN
+ ,grant_type=sas_services
+ ,replace=YES
+ ,debug=0
+ ,contextname=
+ );
+%local oauth_bearer;
+%if &grant_type=detect %then %do;
+ %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
+ %else %let grant_type=sas_services;
+%end;
+%if &grant_type=sas_services %then %do;
+ %let oauth_bearer=oauth_bearer=sas_services;
+ %let &access_token_var=;
+%end;
+%put &sysmacroname: grant_type=&grant_type;
+
+/* initial validation checking */
+%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
+ and &grant_type ne sas_services
+ )
+ ,mac=&sysmacroname
+ ,msg=%str(Invalid value for grant_type: &grant_type)
+)
+%mp_abort(iftrue=(%mf_isblank(&path)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(path value must be provided)
+)
+%mp_abort(iftrue=(%length(&path)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(path value must be provided)
+)
+%mp_abort(iftrue=(%mf_isblank(&name)=1)
+ ,mac=&sysmacroname
+ ,msg=%str(name value must be provided)
+)
+
+options noquotelenmax;
+
+* remove any trailing slash ;
+%if "%substr(&path,%length(&path),1)" = "/" %then
+ %let path=%substr(&path,1,%length(&path)-1);
+
+/* ensure folder exists */
+%put &sysmacroname: Path &path being checked / created;
+%mv_createfolder(path=&path)
+
+%local base_uri; /* location of rest apis */
+%let base_uri=%mf_getplatform(VIYARESTAPI);
+
+/* fetching folder details for provided path */
+%local fname1;
+%let fname1=%mf_getuniquefileref();
+proc http method='GET' out=&fname1 &oauth_bearer
+ url="&base_uri/folders/folders/@item?path=&path";
+%if &grant_type=authorization_code %then %do;
+ headers "Authorization"="Bearer &&&access_token_var";
+%end;
+run;
+%if &debug %then %do;
+ data _null_;
+ infile &fname1;
+ input;
+ putlog _infile_;
+ run;
+%end;
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+/* path exists. Grab follow on link to check members */
+%local libref1;
+%let libref1=%mf_getuniquelibref();
+libname &libref1 JSON fileref=&fname1;
+
+data _null_;
+ set &libref1..links;
+ if rel='members' then call symputx('membercheck',quote("&base_uri"!!trim(href)),'l');
+ else if rel='self' then call symputx('parentFolderUri',href,'l');
+run;
+data _null_;
+ set &libref1..root;
+ call symputx('folderid',id,'l');
+run;
+%local fname2;
+%let fname2=%mf_getuniquefileref();
+proc http method='GET'
+ out=&fname2
+ &oauth_bearer
+ url=%unquote(%superq(membercheck));
+ headers
+ %if &grant_type=authorization_code %then %do;
+ "Authorization"="Bearer &&&access_token_var"
+ %end;
+ 'Accept'='application/vnd.sas.collection+json'
+ 'Accept-Language'='string';
+%if &debug=1 %then %do;
+ debug level = 3;
+%end;
+run;
+/*data _null_;infile &fname2;input;putlog _infile_;run;*/
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+
+%if %upcase(&replace)=YES %then %do;
+ %mv_deletejes(path=&path, name=&name)
+%end;
+%else %do;
+ /* check that job does not already exist in that folder */
+ %local libref2;
+ %let libref2=%mf_getuniquelibref();
+ libname &libref2 JSON fileref=&fname2;
+ %local exists; %let exists=0;
+ data _null_;
+ set &libref2..items;
+ if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then
+ call symputx('exists',1,'l');
+ run;
+ %mp_abort(iftrue=(&exists=1)
+ ,mac=&sysmacroname
+ ,msg=%str(Job &name already exists in &path)
+ )
+ libname &libref2 clear;
+%end;
+
+/* set up the body of the request to create the service */
+%local fname3;
+%let fname3=%mf_getuniquefileref();
+data _null_;
+ file &fname3 TERMSTR=' ';
+ length string $32767;
+ string=cats('{"version": 0,"name":"'
+ ,"&name"
+ ,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"'
+ ,',"type":"CHARACTER","defaultValue":"false"}');
+ context=quote(cats(symget('contextname')));
+ if context ne '""' then do;
+ string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":'
+ ,context,',"type":"CHARACTER","label":"Context Name","required": false}');
+ end;
+ string=cats(string,'],"code":"');
+ put string;
+run;
+
+
+/* insert the code, escaping double quotes and carriage returns */
+%local x fref freflist;
+%let freflist= &precode &code ;
+%do x=1 %to %sysfunc(countw(&freflist));
+ %let fref=%scan(&freflist,&x);
+ %put &sysmacroname: adding &fref;
+ data _null_;
+ length filein 8 fileid 8;
+ filein = fopen("&fref","I",1,"B");
+ fileid = fopen("&fname3","A",1,"B");
+ rec = "20"x;
+ do while(fread(filein)=0);
+ rc = fget(filein,rec,1);
+ if rec='"' then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'"');rc =fwrite(fileid);
+ end;
+ else if rec='0A'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'r');rc =fwrite(fileid);
+ end;
+ else if rec='0D'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'n');rc =fwrite(fileid);
+ end;
+ else if rec='09'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'t');rc =fwrite(fileid);
+ end;
+ else if rec='5C'x then do;
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ rc =fput(fileid,'\');rc =fwrite(fileid);
+ end;
+ else do;
+ rc =fput(fileid,rec);
+ rc =fwrite(fileid);
+ end;
+ end;
+ rc=fclose(filein);
+ rc=fclose(fileid);
+ run;
+%end;
+
+/* finish off the body of the code file loaded to JES */
+data _null_;
+ file &fname3 mod TERMSTR=' ';
+ put '"}';
+run;
+
+/* now we can create the job!! */
+%local fname4;
+%let fname4=%mf_getuniquefileref();
+proc http method='POST'
+ in=&fname3
+ out=&fname4
+ &oauth_bearer
+ url="&base_uri/jobDefinitions/definitions?parentFolderUri=&parentFolderUri";
+ headers 'Content-Type'='application/vnd.sas.job.definition+json'
+ %if &grant_type=authorization_code %then %do;
+ "Authorization"="Bearer &&&access_token_var"
+ %end;
+ "Accept"="application/vnd.sas.job.definition+json";
+%if &debug=1 %then %do;
+ debug level = 3;
+%end;
+run;
+/*data _null_;infile &fname4;input;putlog _infile_;run;*/
+%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 201)
+ ,mac=&sysmacroname
+ ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
+)
+/* clear refs */
+filename &fname1 clear;
+filename &fname2 clear;
+filename &fname3 clear;
+filename &fname4 clear;
+libname &libref1 clear;
+
+/* get the url so we can give a helpful log message */
+%local url;
+data _null_;
+ if symexist('_baseurl') then do;
+ url=symget('_baseurl');
+ if subpad(url,length(url)-9,9)='SASStudio'
+ then url=substr(url,1,length(url)-11);
+ else url="&systcpiphostname";
+ end;
+ else url="&systcpiphostname";
+ call symputx('url',url);
+run;
+
+
+%put &sysmacroname: Job &name successfully created in &path;
+%put &sysmacroname:;
+%put &sysmacroname: Check it out here:;
+%put &sysmacroname:;%put;
+%put &url/SASJobExecution?_PROGRAM=&path/&name;%put;
+%put &sysmacroname:;
+%put &sysmacroname:;
+
+%mend;
diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas
index bc4f0e9..3494d36 100644
--- a/viya/mv_createwebservice.sas
+++ b/viya/mv_createwebservice.sas
@@ -1,5 +1,5 @@
/**
- @file mv_createwebservice.sas
+ @file
@brief Creates a JobExecution web service if it doesn't already exist
@details
Code is passed in as one or more filerefs.